diff --git a/im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java b/im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java new file mode 100644 index 0000000..7bd6704 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.bx.implatform.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 防止表单重复提交注解 + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit { + + + /** + * 间隔时间,小于此时间视为重复提交 + */ + int interval() default 5000; + + /** + * 间隔时间单位 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; + + /** + * 提示消息 + */ + String message() default "请勿提交重复请求"; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java b/im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java new file mode 100644 index 0000000..cdd566f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java @@ -0,0 +1,100 @@ +package com.bx.implatform.aspect; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson.JSON; +import com.bx.implatform.annotation.RepeatSubmit; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.session.SessionContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collection; +import java.util.Map; +import java.util.StringJoiner; + +/** + * @author: blue + * @date: 2024-12-08 + * @version: 1.0 + */ + +@Aspect +@Component +@AllArgsConstructor +public class RepeatSubmitAspect { + + private final RedisTemplate redisTemplate; + + @Before("@annotation(repeatSubmit)") + public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { + // 如果注解不为0 则使用注解数值 + long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); + HttpServletRequest request = + ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); + String url = request.getRequestURL().toString(); + Long userId = SessionContext.getSession().getUserId(); + String reqParams = argsArrayToString(point.getArgs()); + String md5 = SecureUtil.md5(StrUtil.join(":", userId, url, reqParams)); + // 唯一标识 + String key = String.join(":",RedisKey.IM_REPEAT_SUBMIT,md5) ; + if(redisTemplate.hasKey(key)){ + throw new GlobalException(repeatSubmit.message()); + } + redisTemplate.opsForValue().set(key,1,repeatSubmit.interval(),repeatSubmit.timeUnit()); + } + + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringJoiner params = new StringJoiner(" "); + if (ArrayUtil.isEmpty(paramsArray)) { + return params.toString(); + } + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + params.add(JSON.toJSONString(o)); + } + } + return params.toString(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection)o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map)o; + for (Object value : map.values()) { + return value instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java b/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java index 17ddd9f..3162ea4 100644 --- a/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java +++ b/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java @@ -47,9 +47,10 @@ public final class RedisKey { * 缓存群聊成员id */ public static final String IM_CACHE_GROUP_MEMBER_ID = "im:cache:group_member_ids"; + /** - * 分布式锁前缀 + * 重复提交 */ - public static final String IM_LOCK_RTC_GROUP = "im:lock:rtc:group"; + public static final String IM_REPEAT_SUBMIT = "im:repeat:submit"; } diff --git a/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java b/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java index e66be74..c16f692 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java @@ -1,5 +1,6 @@ package com.bx.implatform.controller; +import com.bx.implatform.annotation.RepeatSubmit; import com.bx.implatform.entity.Friend; import com.bx.implatform.result.Result; import com.bx.implatform.result.ResultUtils; @@ -39,6 +40,7 @@ public class FriendController { } + @RepeatSubmit @PostMapping("/add") @Operation(summary = "添加好友", description = "双方建立好友关系") public Result addFriend(@NotNull(message = "好友id不可为空") @RequestParam Long friendId) { diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java index 7f29c23..dc0557b 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java @@ -1,5 +1,6 @@ package com.bx.implatform.controller; +import com.bx.implatform.annotation.RepeatSubmit; import com.bx.implatform.result.Result; import com.bx.implatform.result.ResultUtils; import com.bx.implatform.service.GroupService; @@ -23,6 +24,7 @@ public class GroupController { private final GroupService groupService; + @RepeatSubmit @Operation(summary = "创建群聊", description = "创建群聊") @PostMapping("/create") public Result createGroup(@Valid @RequestBody GroupVO vo) { diff --git a/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java index 36a7a43..84a73e5 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java @@ -1,5 +1,6 @@ package com.bx.implatform.controller; +import com.bx.implatform.annotation.RepeatSubmit; import com.bx.implatform.dto.LoginDTO; import com.bx.implatform.dto.ModifyPwdDTO; import com.bx.implatform.dto.RegisterDTO; @@ -34,6 +35,7 @@ public class LoginController { return ResultUtils.success(vo); } + @RepeatSubmit @PostMapping("/register") @Operation(summary = "用户注册", description = "用户注册") public Result register(@Valid @RequestBody RegisterDTO dto) {