若依框架实现后端防止用户重复点击

文章介绍了如何在若依框架中通过自定义注解和切面编程,结合Redis缓存来防止用户重复点击。主要步骤包括创建自定义注解RepeatClick,定义切面RepeatClickAspect以处理注解逻辑,并在Controller中应用注解进行测试。

若依框架实现后端防止用户重复点击

基于自定义注解、切面、Redis实现

1. 添加自定义注解:

代码放置位置:com/ruoyi/common/annotation/RepeatClick.java

time: 时间默认0;

unit:单位默认 秒;

key: 默认空字符串

package com.ruoyi.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * @author yizhi
 */
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatClick {
	/**
	 * 时间
	 */
    int time() default 0;

	/**
	 * 时间单位,默认秒
	 */
	TimeUnit unit() default TimeUnit.SECONDS;

	/**
	 * 默认会校验的数据
	 */
	String key() default "";
}

2. 添加自定义切面:

代码放置位置:com/ruoyi/framework/aspectj/RepeatClickAspect.java

基于注解和Redis实现防止重复点击

package com.ruoyi.framework.aspectj;

import com.ruoyi.common.annotation.RepeatClick;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SecurityUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.UUID;

/**
 * @author yizhi
 */
@Aspect
@Component
@Log4j2
public class RepeatClickAspect {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 方式一
     *
     * @param joinPoint
     * @param repeatClick
     * @return
     * @throws Throwable
     */
    @Around("@annotation(repeatClick)")
    public Object repeatClick(ProceedingJoinPoint joinPoint, RepeatClick repeatClick) throws Throwable {
        System.out.println("进入切面了");
        Object[] args = joinPoint.getArgs();
        // 重复点击注解为空  跳过
        if (ObjectUtils.isEmpty(repeatClick)) {
            return joinPoint.proceed(args);
        }
        // 使用错误提示
        if (ObjectUtils.isEmpty(repeatClick.key()) || repeatClick.time() == 0) {
            log.error("注意:存在自定义注解,使用异常:请检查是否设置key, time, unit");
            return joinPoint.proceed(args);
        }
        String repeatClickKey = repeatClick.key() + SecurityUtils.getLoginUser().getUserId();
        if (Boolean.TRUE.equals(redisTemplate.hasKey(repeatClickKey))) {
            return AjaxResult.error("请勿重复点击");
        }
        redisTemplate.opsForValue().set(repeatClickKey, UUID.randomUUID().toString());
        redisTemplate.expire(repeatClickKey, repeatClick.time(), repeatClick.unit());
        return joinPoint.proceed(args);
    }

    /**
     * 方式二
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
//    @Around("@annotation(com.ruoyi.framework.annotation.RepeatClick)")
//    public Object repeatClick(ProceedingJoinPoint joinPoint) throws Throwable {
//        System.out.println("进入切面了");
//        Object[] args = joinPoint.getArgs();
//        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//        RepeatClick repeatClick = signature.getMethod().getAnnotation(RepeatClick.class);
//        // 重复点击注解为空  跳过
//        if (ObjectUtils.isEmpty(repeatClick)) {
//            return joinPoint.proceed(args);
//        }
//        // 使用错误提示
//        if (ObjectUtils.isEmpty(repeatClick.key()) || repeatClick.time() == 0) {
//            log.error("注意:存在自定义注解,使用异常:请检查是否设置key, time, unit");
//            return joinPoint.proceed(args);
//        }
//        String repeatClickKey = repeatClick.key() + SecurityUtils.getLoginUser().getUserId();
//        if (Boolean.TRUE.equals(redisTemplate.hasKey(repeatClickKey))) {
//            return AjaxResult.error("请勿重复点击");
//        }
//        redisTemplate.opsForValue().set(repeatClickKey, UUID.randomUUID().toString());
//        redisTemplate.expire(repeatClickKey, repeatClick.time(), repeatClick.unit());
//        return joinPoint.proceed(args);
//    }
}

3. 最后在controller中添加注解进行测试

key: 我给自己规定填写 —完整接口名称(因为唯一)

time和unit合起来一起使用,unit默认是秒,那这个就是10秒

如果unit 设置为 分钟,那这个就是十分钟

@RepeatClick(key = "bsLable.ceshi", time = 10)

@RepeatClick(key = "bsLable.ceshi", time = 10, unit = TimeUnit.MINUTES)

4. 自行查看测试结果

<think> 首先,用户的问题是:“我想了解若依框架后端获取参数设置的方法”,意思是“我想了解若依框架后端获取参数设置的方法”。这里的“参数设置”可能指的是如何获取请求参数、配置参数或其他设置。 用户提供了两个引用: - 引用[1]:讨论若依框架的功能,包括分页、导入导出、数据处理、权限控制等。 - 引用[2]:讨论若依框架后端的登录流程,特别是security的部分。 系统指令要求: - 所有行内数学表达式必须使用 $...$ 格式(但在这个问题中,可能没有数学表达式)。 - 独立公式必须单独成段并使用 $$...$$(同样,可能不需要)。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如 [^1]。 - 参考用户提供的引用。 用户之前的上下文是系统指令示例,但用户的问题是独立的。我需要基于用户提供的引用和问题来回答。 关键点是:用户想知道若依框架后端如何获取参数设置。这可能涉及到HTTP请求参数的获取、配置文件读取、或其他方式获取参数。 从引用[1]中: - 提到了分页实现(使用PageHelper)、导入导出、数据处理等。 - 具体点:分页查询条件、参数验证拓展等。 - 引用[2]中:登录流程,调用UserDetailsServiceImpl.loadUserByUsername。 因此,我应该从引用中提取相关信息。 在若依框架(基于Spring Boot和Spring Security)中,获取参数设置的方法可能包括: 1. **控制器中获取请求参数**:通过在Controller方法中使用@RequestMapping和相关注解来获取HTTP请求参数。 2. **配置文件读取**:从application.yml或application.properties中读取配置。 3. **方法参数绑定**:使用@RequestParam、@PathVariable、@RequestBody等注解。 4. **自定义参数验证**:引用[1]中提到“参数验证拓展”,这可能涉及到使用Java Bean Validation框架。 5. **数据权限控制**:引用[1]中提到“数据的具体过滤”,可能涉及到基于用户权限过滤参数。 回答结构: - 结构化清晰,逐步解决问题。 - 使用中文。 - 结束时添加相关问题。 - 引用时添加标识。 大纲解答: 1. **介绍获取参数的方法**:简要说明在若依框架后端获取参数的不同方式。 2. **HTTP请求参数获取**:使用@RequestParam等注解。 3. **路径参数获取**:使用@PathVariable。 4. **请求体参数获取**:使用@RequestBody。 5. **配置文件参数获取**:使用@Value或Environment。 6. **结合引用**:引用[1]中提到“分页实现”和“参数验证”,这可能涉及参数获取。 7. **代码示例**:提供简单的代码片段来展示。 8. **相关问题生成**:结束时添加相关问题。 确保回答真实可靠:基于一般Spring Boot知识,因为若依框架是Spring Boot的扩展。 最终答案结构: - 正文:解释方法。 - 代码块:如果有代码。 - 相关问题。 引用标识:在相关部分自然地添加 [^1] 或 [^2]。 </think> ### 若依框架后端获取参数设置的方法 若依框架(Ruoyi)是一个基于 Spring Boot 和 Spring Security 的前后台分离框架后端获取参数设置主要涉及从 HTTP 请求中提取参数(如查询参数、路径参数、请求体数据),以及从配置文件读取设置。这些方法都遵循 Spring MVC 的注解机制,并结合了框架的扩展功能(如参数验证和数据过滤)。以下我将逐步解释常见方法,确保回答基于框架实际实现参考您提供的引用内容。 #### 1. **HTTP 请求参数获取** - **使用 `@RequestParam` 注解**:这是最常用的方法,用于从 URL 查询字符串中提取参数。适用于 GET 或 POST 请求的简单参数传递。 - 示例:在 Controller 方法中定义一个参数,使用 `@RequestParam` 注解指定参数名。如果参数可选,可以设置 `required = false`。 - 代码示例: ```java // 在 Controller 类中 @GetMapping("/user/list") public TableDataInfo listUsers(@RequestParam(required = false) String username, @RequestParam(defaultValue = "1") Integer pageNum) { // 使用参数查询用户列表 startPage(); // 若依分页实现引用 [^1] 中的 PageHelper List<User> users = userService.selectUserList(username); return getDataTable(users); } ``` - **说明**:若依框架的分页实现依赖于 PageHelper(引用 [^1] 中提到的分页实现),`startPage()` 方法会自动从请求参数中读取 `pageNum` 和 `pageSize`,无需手动处理。 #### 2. **路径参数获取** - **使用 `@PathVariable` 注解**:用于从 URL 路径中提取变量值。适用于 RESTful 风格的 API。 - 示例:URL 如 `/user/{id}`,通过 `@PathVariable` 绑定到方法参数。 - 代码示例: ```java @GetMapping("/user/{id}") public AjaxResult getUserById(@PathVariable Long id) { User user = userService.selectUserById(id); return AjaxResult.success(user); } ``` - **说明**:这种方法常用于资源标识符的传递,结合引用 [^1] 中的 “异常处理” 功能,框架会自动处理参数无效的异常(如 ID 不存在)。 #### 3. **请求体参数获取(JSON 或其他格式)** - **使用 `@RequestBody` 注解**:用于从 HTTP 请求体中提取数据,适用于 POST 或 PUT 请求传输 JSON 或 XML 格式的参数对象。 - 示例:定义一个 Java Bean(DTO 或 Entity),使用 `@RequestBody` 绑定请求体数据。 - 代码示例: ```java @PostMapping("/user/save") public AjaxResult saveUser(@RequestBody User user) { // 参数验证:引用 [^1] 提到的参数验证拓展 if (user.getUsername() == null) { throw new ServiceException("用户名不能为空"); } userService.saveUser(user); return AjaxResult.success("用户保存成功"); } ``` - **说明**:若依框架强化了参数验证功能(引用 [^1] 中的 “参数验证拓展”),可以通过自定义验证器或使用 Java Bean Validation(如 `@NotNull` 注解)确保数据完整性。 #### 4. **配置文件参数获取** - **使用 `@Value` 或 `Environment`**:若依框架支持从 `application.yml` 或 `application.properties` 读取全局设置,如数据库连接、密钥等。 - 示例:通过 `@Value` 注入配置值,或使用 `Environment` 对象动态读取。 - 代码示例: ```java @Value("${ruoyi.jwt.secret}") private String jwtSecret; // 从配置文件读取 JWT 密钥 // 或在 Service 类中 @Autowired private Environment env; public String getConfigValue(String key) { return env.getProperty(key); } ``` - **说明**:结合引用 [^2] 中的登录流程,配置参数常用于安全模块(如 JWT token密钥),确保参数设置与系统安全集成。 #### 5. **其他相关方法** - **数据权限过滤**:若依框架支持基于用户角色过滤数据参数(引用 [^1] 中的 “数据的具体过滤”),通过注解如 `@DataScope` 实现表级别的参数限制。 - 示例:在 Service 层添加注解,自动过滤查询参数。 - 代码示例: ```java @DataScope(deptAlias = "d", userAlias = "u") public List<User> selectUserList(User user) { // 查询时会自动添加数据权限条件 return userMapper.selectUserList(user); } ``` - **参数验证拓展**:框架扩展了 Spring 的验证机制(引用 [^1]),支持自定义注解如 `@Excel` 来处理导入导出时的参数设置(例如导出自定义隐藏列)。 #### 最佳实践和注意事项 - **参数验证**:始终在 Controller 或 Service 层进行参数验证,防止无效数据入库(引用 [^1] 的参数验证功能)。若依框架的异常处理机制(如 `GlobalExceptionHandler`)会自动捕获并返回错误响应。 - **性能考虑**:对于高频请求,避免在 Controller 中处理复杂逻辑;使用 Service 层分离业务逻辑。 - **安全提示**:获取参数时注意防范注入攻击,框架内置了 XSS 过滤和 SQL 防护(引用 [^1] 的系统日志和防重复提交功能)。 通过这些方法,若依框架后端可以高效、安全地处理参数设置,适用于各种业务场景如用户管理、数据查询等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yizhi-w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值