灰气球

灰气球

Java 简单疲劳度模型设计

179
2023-04-01

应用场景

疲劳度模型在很多场景下都是必不可少的,比如限制用户在一定时间内发送短信的次数、限制用户在一定时间内请求接口的次数等等。通过限制用户的操作次数,可以避免用户过度使用某个功能或接口,从而保护系统的稳定性和安全性。

设计与功能

这个疲劳度模型包含了两个维度的疲劳度配置:全平台维度和userId维度。全平台维度的疲劳度配置是所有用户共享的,而userId维度的疲劳度配置是每个用户单独拥有的。模型中的consume方法用于消耗一个疲劳度,每次调用会检查全平台维度和userId维度的疲劳度是否已经用完。如果还有剩余,则消耗一个疲劳度并返回true;否则返回false。模型中还包含了设置全平台维度和userId维度疲劳度配置的方法。

用法

使用这个疲劳度模型非常简单。首先,需要创建一个FatigueModel对象。可以使用默认的配置,也可以自定义配置。然后,在需要限制操作次数的地方调用consume方法,传入userId参数即可。如果consume方法返回true,则说明还有剩余的疲劳度,可以进行操作;如果返回false,则说明疲劳度已经用完,需要等待疲劳度周期结束后才能进行操作。

编码实现

用户维度疲劳度模型默认一天20次
全平台维度的疲劳度默认一天10000次

package com.eden.core.model;

import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 疲劳度模型
 *
 * @author Eden
 */
@Slf4j
@NoArgsConstructor
public class FatigueModel {

    /**
     * 全平台维度的疲劳度配置-默认一天10000次
     */
    private int globalLimit = 10000;
    /**
     * 全平台维度的疲劳度配置-疲劳度周期 默认一天
     */
    private long globalPeriod = 24 * 60 * 60 * 1000;

    /**
     * 全平台维度的疲劳度配置-当前疲劳度次数
     */
    private int currentGlobalLimit = globalLimit;
    /**
     * 全平台疲劳度周期的开始时间
     */
    private long globalStartTime = System.currentTimeMillis();

    /**
     * userId维度的疲劳配置
     */
    private final Map<String, Integer> userLimits = new HashMap<>();
    private final Map<String, Long> userStartTimes = new HashMap<>();

    /**
     * userId维度的疲劳配置-默认一天20次
     */
    private int userIdLimit = 20;

    /**
     * userId维度的疲劳配置-疲劳度周期为一天
     */
    private long userIdPeriod = 24 * 60 * 60 * 1000;

    public FatigueModel(int globalLimit, int userIdLimit) {
        this.globalLimit = globalLimit;
        this.currentGlobalLimit = globalLimit;
        this.userIdLimit = userIdLimit;
    }

    public FatigueModel(int globalLimit, long globalPeriod, int userIdLimit, long userIdPeriod) {
        this.globalLimit = globalLimit;
        this.currentGlobalLimit = globalLimit;
        this.globalPeriod = globalPeriod;
        this.userIdLimit = userIdLimit;
        this.userIdPeriod = userIdPeriod;
    }

    /**
     * 用户调用一次,消耗一个疲劳度
     */
    public synchronized boolean consume(String userId) {
        // 获取当前时间
        long now = System.currentTimeMillis();

        // 更新全平台维度的疲劳度周期
        if (now - globalStartTime >= globalPeriod) {
            // 重置全平台维度的疲劳度
            currentGlobalLimit = globalLimit;
            // 更新全平台疲劳度周期的开始时间
            globalStartTime = now;
        }

        // 判断全平台维度的疲劳度是否已经用完
        if (currentGlobalLimit <= 0) {
            log.info(String.format("consume,currentGlobalLimit <= 0,userId=%s,now=%s,currentGlobalLimit=%s,globalStartTime=%s",
                    userId, now, currentGlobalLimit, globalStartTime));
            return false;
        }

        // 判断userId维度的疲劳度是否已经用完
        int limit = fetchUserLimit(userId);
        if (limit <= 0) {
            return false;
        }

        // 更新全平台维度的疲劳度和userId维度的疲劳度
        currentGlobalLimit--;
        userLimits.put(userId, limit - 1);
        return true;
    }

    /**
     * 设置全平台维度的疲劳度配置
     */
    public synchronized void setGlobalLimit(int limit, long period) {
        globalLimit = limit;
        currentGlobalLimit = limit;
        globalPeriod = period;
        globalStartTime = System.currentTimeMillis();
    }

    /**
     * 设置userId维度的疲劳度配置
     */
    public synchronized void setUserLimit(String userId, int limit, long period) {
        userLimits.put(userId, limit);
        userStartTimes.put(userId, System.currentTimeMillis() - period);
    }

    /**
     * 充值userId维度的疲劳度
     */
    public synchronized void addUserLimit(String userId, int limit) {
        Integer userLimit = userLimits.getOrDefault(userId, this.userIdLimit);
        userLimits.put(userId, userLimit + limit);
    }

    /**
     * 获取用户当前剩余疲劳度
     */
    public int fetchUserLimit(String userId) {
        long now = System.currentTimeMillis();
        // 获取用户的疲劳度周期和周期内可用次数
        int limit = userLimits.getOrDefault(userId, userIdLimit);
        Long userStartTime = userStartTimes.get(userId);
        // 判断是否在疲劳度周期内,如果不在则重置周期内可用次数
        if (Objects.isNull(userStartTime) || (now - userStartTime >= userIdPeriod)) {
            userStartTimes.put(userId, now);
            limit = userIdLimit;
            userLimits.put(userId, limit);
        }
        log.info(String.format("fetchUserLimit,userId=%s,now=%s,limit=%s,userStartTime=%s",
                userId, now, limit, userStartTime));
        return limit;
    }

    /**
     * 校验用户疲劳度
     */
    public boolean checkUserLimit(String userId) {
        return fetchUserLimit(userId) > 0;
    }
}