From e2ad1ead1b06b31ae6ad988938f43c781af68c83 Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sat, 16 Sep 2023 18:03:58 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E4=B8=8D=E6=A0=A1=E9=AA=8C=E9=82=AE=E7=AE=B1=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E6=BF=80=E6=B4=BB=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=B3=A8=E5=86=8C=E4=B8=8D=E5=8F=91=E9=80=81=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E9=82=AE=E4=BB=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/controller/UserController.java | 46 ------------------- .../controller/request/user/ActivateDTO.java | 19 -------- .../user/SendRegistrationTokenDTO.java | 10 ---- .../maa/backend/service/EmailService.java | 20 -------- .../plus/maa/backend/service/UserService.java | 41 +++-------------- 5 files changed, 7 insertions(+), 129 deletions(-) delete mode 100644 src/main/java/plus/maa/backend/controller/request/user/ActivateDTO.java delete mode 100644 src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java diff --git a/src/main/java/plus/maa/backend/controller/UserController.java b/src/main/java/plus/maa/backend/controller/UserController.java index c605bab7..90858241 100644 --- a/src/main/java/plus/maa/backend/controller/UserController.java +++ b/src/main/java/plus/maa/backend/controller/UserController.java @@ -45,40 +45,6 @@ public class UserController { @Value("${maa-copilot.jwt.header}") private String header; - /** - * 激活token中的用户 - * - * @param activateDTO 激活码 - * @return 成功响应 - */ - @Operation(summary = "激活用户") - @ApiResponse(description = "激活用户结果") - @SecurityRequirement(name = SpringDocConfig.SECURITY_SCHEME_NAME) - @PostMapping("/activate") - public MaaResult activate( - @Parameter(description = "激活用户请求") @Valid @RequestBody ActivateDTO activateDTO - ) { - // FIXME 应改为从 body 中获取, 解决激活——登录悖论,待讨论 - var userId = helper.requireUserId(); - userService.activateUser(userId, activateDTO); - return MaaResult.success(); - } - - /** - * 注册完成后发送邮箱激活码 - * - * @return null - */ - @Operation(summary = "完成注册后发送邮箱激活码") - @ApiResponse(description = "激活码发送结果") - @SecurityRequirement(name = SpringDocConfig.SECURITY_SCHEME_NAME) - @PostMapping("/activate/request") - public MaaResult activateRequest() { - // FIXME 完成注册后发送激活码不应该由客户端请求 - userService.sendActiveCodeByEmail(helper.requireUserId()); - return MaaResult.success(); - } - /** * 更新当前用户的密码(根据原密码) * @@ -171,18 +137,6 @@ public MaaResult register(@Parameter(description = "用户注册请 return MaaResult.success(userService.register(user)); } - /** - * 获得注册时的验证码 - */ - @PostMapping("/sendRegistrationToken") - @Operation(summary = "注册时发送验证码") - @ApiResponse(description = "发送验证码结果", responseCode = "204") - public MaaResult sendRegistrationToken(@Parameter(description = "发送注册验证码请求") @RequestBody SendRegistrationTokenDTO regDTO) { - //FIXME: 增加频率限制或者 captcha - emailService.sendVCode(regDTO.getEmail()); - return new MaaResult<>(204, null, null); - } - /** * 用户登录 * diff --git a/src/main/java/plus/maa/backend/controller/request/user/ActivateDTO.java b/src/main/java/plus/maa/backend/controller/request/user/ActivateDTO.java deleted file mode 100644 index 14baea77..00000000 --- a/src/main/java/plus/maa/backend/controller/request/user/ActivateDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package plus.maa.backend.controller.request.user; - -import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -/** - * @author john180 - */ -@Data -@Accessors(chain = true) -@NoArgsConstructor -@AllArgsConstructor -public class ActivateDTO { - @NotBlank(message = "激活码不能为空") - private String token; -} diff --git a/src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java b/src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java deleted file mode 100644 index 11851c5f..00000000 --- a/src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java +++ /dev/null @@ -1,10 +0,0 @@ -package plus.maa.backend.controller.request.user; - -import jakarta.validation.constraints.Email; -import lombok.Data; - -@Data -public class SendRegistrationTokenDTO { - @Email(message = "邮箱格式错误") - private String email; -} diff --git a/src/main/java/plus/maa/backend/service/EmailService.java b/src/main/java/plus/maa/backend/service/EmailService.java index c04e21b6..649a0ae0 100644 --- a/src/main/java/plus/maa/backend/service/EmailService.java +++ b/src/main/java/plus/maa/backend/service/EmailService.java @@ -10,7 +10,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import org.springframework.util.Assert; import plus.maa.backend.common.bo.EmailBusinessObject; import plus.maa.backend.config.external.MaaCopilotProperties; import plus.maa.backend.config.external.Mail; @@ -102,25 +101,6 @@ public void verifyVCode(String email, String vcode) { redisCache.removeCache("vCodeEmail:" + email); } - /** - * 验证发到某个邮箱的验证码 - * - * @param email 邮箱 - * @param vcode 验证码 - * @param clearVCodeOnSuccess 验证成功是否删除验证码 - * @return 是否一致 - */ - - public boolean verifyVCode2(String email, String vcode, boolean clearVCodeOnSuccess) { - // FIXME:可能出现多线程数据争用问题,想办法用redis的一些方法直接比较完删除 - String cacheVCode = redisCache.getCache("vCodeEmail:" + email, String.class); - boolean result = StringUtils.equalsIgnoreCase(cacheVCode, vcode); - if (clearVCodeOnSuccess && result) { - redisCache.removeCache("vCodeEmail:" + email); - } - return result; - } - /** * @param email 发送激活验证邮箱 */ diff --git a/src/main/java/plus/maa/backend/service/UserService.java b/src/main/java/plus/maa/backend/service/UserService.java index 111e8270..2270f81a 100644 --- a/src/main/java/plus/maa/backend/service/UserService.java +++ b/src/main/java/plus/maa/backend/service/UserService.java @@ -58,6 +58,10 @@ public MaaLoginRsp login(LoginDTO loginDTO) { if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) { throw new MaaResultException(401, "用户不存在或者密码错误"); } + // 未激活的用户 + if (Objects.equals(user.getStatus(), 0)) { + throw new MaaResultException(401, "账户未激活"); + } var jwtId = UUID.randomUUID().toString(); var jwtIds = user.getRefreshJwtIds(); @@ -108,36 +112,18 @@ public MaaUserInfo register(RegisterDTO registerDTO) { MaaUser user = new MaaUser(); BeanUtils.copyProperties(registerDTO, user); user.setPassword(encode); - user.setStatus(1); MaaUserInfo userInfo; - if (!emailService.verifyVCode2(user.getEmail(), registerDTO.getRegistrationToken(), false)) { - throw new MaaResultException(MaaStatusCode.MAA_REGISTRATION_CODE_NOT_FOUND); - } try { MaaUser save = userRepository.save(user); userInfo = new MaaUserInfo(save); } catch (DuplicateKeyException e) { throw new MaaResultException(MaaStatusCode.MAA_USER_EXISTS); } + // 发送激活验证邮箱 + emailService.sendActivateUrl(registerDTO.getEmail()); return userInfo; } - /** - * 通过传入的JwtToken来获取当前用户的信息 - * - * @param userId 当前用户 - * @param activateDTO 邮箱激活码 - */ - public void activateUser(@NotNull String userId, ActivateDTO activateDTO) { - userRepository.findById(userId).ifPresent((maaUser) -> { - if (1 == maaUser.getStatus()) return; - var email = maaUser.getEmail(); - emailService.verifyVCode(email, activateDTO.getToken()); - maaUser.setStatus(1); - userRepository.save(maaUser); - }); - } - /** * 更新用户信息 * @@ -151,19 +137,6 @@ public void updateUserInfo(@NotNull String userId, UserInfoUpdateDTO updateDTO) }); } - /** - * 为用户发送激活验证码 - * - * @param userId 用户 id - */ - public void sendActiveCodeByEmail(String userId) { - userRepository.findById(userId).ifPresent((maaUser) -> { - Assert.state(Objects.equals(maaUser.getStatus(), 0), - "用户已经激活,无法再次发送验证码"); - emailService.sendVCode(maaUser.getEmail()); - }); - } - /** * 刷新token * @@ -234,7 +207,7 @@ public void checkUserExistByEmail(String email) { public void activateAccount(EmailActivateReq activateDTO) { String uuid = activateDTO.getNonce(); String email = redisCache.getCache("UUID:" + uuid, String.class); - Assert.notNull(email, "链接已过期"); + Assert.notNull(email, "链接不存在或已过期"); MaaUser user = userRepository.findByEmail(email); try { From 132f22dca7e2fc752e817ffd416bca545677e7b4 Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sat, 16 Sep 2023 19:47:42 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E7=9A=84=E5=A4=9A=E7=BA=BF=E7=A8=8B=E6=95=B0=E6=8D=AE=E4=BA=89?= =?UTF-8?q?=E7=94=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../maa/backend/repository/RedisCache.java | 66 ++++++++++++------- .../maa/backend/service/EmailService.java | 5 +- .../resources/redis-lua/removeKVIfEquals.lua | 6 ++ 3 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 src/main/resources/redis-lua/removeKVIfEquals.lua diff --git a/src/main/java/plus/maa/backend/repository/RedisCache.java b/src/main/java/plus/maa/backend/repository/RedisCache.java index e0aec204..249a6dbc 100644 --- a/src/main/java/plus/maa/backend/repository/RedisCache.java +++ b/src/main/java/plus/maa/backend/repository/RedisCache.java @@ -55,6 +55,8 @@ public class RedisCache { 实际大小这么设计是为了避免频繁的 ZREMRANGEBYRANK 操作 */ private final RedisScript incZSetRedisScript = RedisScript.of(new ClassPathResource("redis-lua/incZSet.lua")); + // 比较与输入的键值对是否相同,相同则删除 + private final RedisScript removeKVIfEqualsScript = RedisScript.of(new ClassPathResource("redis-lua/removeKVIfEquals.lua"), Boolean.class); public void setData(final String key, T value) { setCache(key, value, 0, TimeUnit.SECONDS); @@ -69,15 +71,8 @@ public void setCache(final String key, T value, long timeout) { } public void setCache(final String key, T value, long timeout, TimeUnit timeUnit) { - String json; - try { - json = writeMapper.writeValueAsString(value); - } catch (JsonProcessingException e) { - if (log.isDebugEnabled()) { - log.debug(e.getMessage(), e); - } - return; - } + String json = getJson(value); + if (json == null) return; if (timeout <= 0) { redisTemplate.opsForValue().set(key, json); } else { @@ -94,17 +89,13 @@ public void addSet(final String key, Collection set, long timeout, TimeUn return; } String[] jsonList = new String[set.size()]; - try { - int i = 0; - for (T t : set) { - jsonList[i++] = writeMapper.writeValueAsString(t); - } - } catch (JsonProcessingException e) { - if (log.isDebugEnabled()) { - log.debug(e.getMessage(), e); - } - return; + int i = 0; + for (T t : set) { + String json = getJson(t); + if (json == null) return; + jsonList[i++] = json; } + if (timeout <= 0) { redisTemplate.opsForSet().add(key, jsonList); } else { @@ -144,12 +135,9 @@ public Set getZSetReverse(final String key, long start, long end) { public boolean valueMemberInSet(final String key, T value) { try { - String json = writeMapper.writeValueAsString(value); + String json = getJson(value); + if (json == null) return false; return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, json)); - } catch (JsonProcessingException e) { - if (log.isDebugEnabled()) { - log.debug(e.getMessage(), e); - } } catch (Exception e) { log.error(e.getMessage(), e); } @@ -244,6 +232,21 @@ public void removeCache(String key) { redisTemplate.delete(key); } + /** + * 相同则删除键值对 + * + * @param key 待比较和删除的键 + * @param value 待比较的值 + * @return 是否删除 + */ + public boolean removeKVIfEquals(String key, T value) { + String json = getJson(value); + if (json == null) return false; + return Boolean.TRUE.equals( + redisTemplate.execute(removeKVIfEqualsScript, List.of(key), json) + ); + } + /** * 模糊删除缓存。 * @@ -282,4 +285,19 @@ public void removeCacheByPattern(String pattern) { redisTemplate.delete(keysToDelete); } } + + + @Nullable + private String getJson(T value) { + String json; + try { + json = writeMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + if (log.isDebugEnabled()) { + log.debug(e.getMessage(), e); + } + return null; + } + return json; + } } diff --git a/src/main/java/plus/maa/backend/service/EmailService.java b/src/main/java/plus/maa/backend/service/EmailService.java index 649a0ae0..3d93ac56 100644 --- a/src/main/java/plus/maa/backend/service/EmailService.java +++ b/src/main/java/plus/maa/backend/service/EmailService.java @@ -5,7 +5,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; @@ -94,11 +93,9 @@ public void sendVCode(String email) { * @throws MaaResultException 验证码错误 */ public void verifyVCode(String email, String vcode) { - String cacheVCode = redisCache.getCache("vCodeEmail:" + email, String.class); - if (!StringUtils.equalsIgnoreCase(cacheVCode, vcode)) { + if (!redisCache.removeKVIfEquals("vCodeEmail:" + email, vcode.toUpperCase())) { throw new MaaResultException(401, "验证码错误"); } - redisCache.removeCache("vCodeEmail:" + email); } /** diff --git a/src/main/resources/redis-lua/removeKVIfEquals.lua b/src/main/resources/redis-lua/removeKVIfEquals.lua new file mode 100644 index 00000000..09c76db2 --- /dev/null +++ b/src/main/resources/redis-lua/removeKVIfEquals.lua @@ -0,0 +1,6 @@ +local dbValue = redis.call('GET', KEYS[1]) +if dbValue == ARGV[1] then + redis.call('DEL', KEYS[1]) + return true +end +return false \ No newline at end of file From 8ae654a0371b3422c94551327b6bd034932ef2fb Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sat, 16 Sep 2023 21:41:52 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=BF=80=E6=B4=BB=E9=93=BE=E6=8E=A5=E7=9A=84?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 2 +- .../backend/controller/UserController.java | 8 +++++++ .../controller/request/user/LoginDTO.java | 1 + .../controller/request/user/RegisterDTO.java | 2 ++ .../request/user/ResendActivateUrlDTO.java | 12 ++++++++++ .../maa/backend/service/EmailService.java | 2 ++ .../plus/maa/backend/service/UserService.java | 24 ++++++++++++++++++- 7 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java diff --git a/src/main/java/plus/maa/backend/config/security/SecurityConfig.java b/src/main/java/plus/maa/backend/config/security/SecurityConfig.java index 0922176b..cfa93a4a 100644 --- a/src/main/java/plus/maa/backend/config/security/SecurityConfig.java +++ b/src/main/java/plus/maa/backend/config/security/SecurityConfig.java @@ -26,7 +26,7 @@ public class SecurityConfig { private static final String[] URL_WHITELIST = { "/user/login", "/user/register", - "/user/sendRegistrationToken" + "/user/resendActivateUrl" }; private static final String[] URL_PERMIT_ALL = { diff --git a/src/main/java/plus/maa/backend/controller/UserController.java b/src/main/java/plus/maa/backend/controller/UserController.java index 90858241..3787a167 100644 --- a/src/main/java/plus/maa/backend/controller/UserController.java +++ b/src/main/java/plus/maa/backend/controller/UserController.java @@ -150,6 +150,14 @@ public MaaResult login(@Parameter(description = "登录请求") @Re return MaaResult.success("登陆成功", userService.login(user)); } + @PostMapping("/resendActivateUrl") + @Operation(summary = "重新获取激活链接") + @ApiResponse(description = "发送激活链接") + public MaaResult resendActivateUrl(@Parameter(description = "重新获取激活链接请求") @RequestBody @Valid ResendActivateUrlDTO activateUrlDTO) { + userService.resendActivateUrl(activateUrlDTO); + return MaaResult.success(); + } + @GetMapping("/activateAccount") @Operation(summary = "激活账号") @ApiResponse(description = "激活账号结果") diff --git a/src/main/java/plus/maa/backend/controller/request/user/LoginDTO.java b/src/main/java/plus/maa/backend/controller/request/user/LoginDTO.java index af64ceba..2c2f8270 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/LoginDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/LoginDTO.java @@ -15,6 +15,7 @@ @NoArgsConstructor @AllArgsConstructor public class LoginDTO { + @NotBlank(message = "邮箱格式错误") @Email(message = "邮箱格式错误") private String email; @NotBlank(message = "请输入用户密码") diff --git a/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java b/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java index 45770cde..e65ccd42 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java @@ -1,6 +1,7 @@ package plus.maa.backend.controller.request.user; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -15,6 +16,7 @@ @NoArgsConstructor @AllArgsConstructor public class RegisterDTO { + @NotBlank(message = "邮箱格式错误") @Email(message = "邮箱格式错误") private String email; @Length(min = 4, max = 24, message = "用户名长度应在2-24位之间") diff --git a/src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java b/src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java new file mode 100644 index 00000000..6acbf9d8 --- /dev/null +++ b/src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java @@ -0,0 +1,12 @@ +package plus.maa.backend.controller.request.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class ResendActivateUrlDTO { + @NotBlank(message = "邮箱格式错误") + @Email(message = "邮箱格式错误") + private String email; +} diff --git a/src/main/java/plus/maa/backend/service/EmailService.java b/src/main/java/plus/maa/backend/service/EmailService.java index 3d93ac56..17e5d257 100644 --- a/src/main/java/plus/maa/backend/service/EmailService.java +++ b/src/main/java/plus/maa/backend/service/EmailService.java @@ -117,6 +117,8 @@ public void sendActivateUrl(String email) { } // 存redis redisCache.setCache("UUID:" + uuid, email, expire); + // 一个链接过期周期最多重发十条,记录已发送的邮箱以及间隔时间 + redisCache.setCache("HasBeenSentAU:" + email, expire / 10, expire / 10); } @Async diff --git a/src/main/java/plus/maa/backend/service/UserService.java b/src/main/java/plus/maa/backend/service/UserService.java index 2270f81a..8221ad93 100644 --- a/src/main/java/plus/maa/backend/service/UserService.java +++ b/src/main/java/plus/maa/backend/service/UserService.java @@ -60,7 +60,7 @@ public MaaLoginRsp login(LoginDTO loginDTO) { } // 未激活的用户 if (Objects.equals(user.getStatus(), 0)) { - throw new MaaResultException(401, "账户未激活"); + throw new MaaResultException(MaaStatusCode.MAA_USER_NOT_ENABLED); } var jwtId = UUID.randomUUID().toString(); @@ -199,6 +199,28 @@ public void checkUserExistByEmail(String email) { } } + /** + * 重新发送激活邮件 + */ + public void resendActivateUrl(ResendActivateUrlDTO activateUrlDTO) { + // 限制请求间隔 + Integer interval = redisCache.getCache("HasBeenSentAU:" + activateUrlDTO.getEmail(), Integer.class); + if (interval != null) { + throw new MaaResultException(403, String.format("发送激活邮件的请求至少需要间隔 %d 秒", interval)); + } + // 判断用户是否存在 + MaaUser maaUser = userRepository.findByEmail(activateUrlDTO.getEmail()); + if (maaUser == null) { + throw new MaaResultException(MaaStatusCode.MAA_USER_NOT_FOUND); + } + // 禁止重复激活 + if (!Objects.equals(maaUser.getStatus(), 0)) { + throw new MaaResultException(403, "用户已经激活,无法再次发送验证码"); + } + // 重新发送验证码 + emailService.sendActivateUrl(activateUrlDTO.getEmail()); + } + /** * 激活账户 * From 6fd40153804dd1165fb8efba6ce780f4254c5a2f Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sun, 17 Sep 2023 11:18:25 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=AE=9A=E6=9C=9F=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=9C=AA=E6=BF=80=E6=B4=BB=E8=B4=A6=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/repository/UserRepository.java | 2 ++ .../task/DeleteNotEnabledMaaUsersTask.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java diff --git a/src/main/java/plus/maa/backend/repository/UserRepository.java b/src/main/java/plus/maa/backend/repository/UserRepository.java index 35112d39..f8dbaee5 100644 --- a/src/main/java/plus/maa/backend/repository/UserRepository.java +++ b/src/main/java/plus/maa/backend/repository/UserRepository.java @@ -20,6 +20,8 @@ public interface UserRepository extends MongoRepository { */ MaaUser findByEmail(String email); + void deleteAllByStatusIs(Integer status); + default Map findByUsersId(List userId) { return findAllById(userId) .stream().collect(Collectors.toMap(MaaUser::getUserId, Function.identity())); diff --git a/src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java b/src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java new file mode 100644 index 00000000..c66989e7 --- /dev/null +++ b/src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java @@ -0,0 +1,28 @@ +package plus.maa.backend.task; + +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import plus.maa.backend.repository.UserRepository; + + +/** + * 定期删除未激活的账户,释放数据库空间 + * + * @author Lixuhuilll + * created on 2023.09.17 + */ +@Component +@RequiredArgsConstructor +public class DeleteNotEnabledMaaUsersTask { + + final UserRepository userRepository; + + /** + * 每三天的凌晨 0 点整清理一次 + */ + @Scheduled(cron = "0 0 0 */3 * ?") + public void deleteNotEnabledMaaUsers() { + userRepository.deleteAllByStatusIs(0); + } +} From f5aa834d859ad6277cc07a492a1e30d397c1d076 Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sat, 16 Sep 2023 21:41:52 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E6=94=B9=E5=9B=9E=E7=BB=B4=E6=8C=81?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E6=80=9D=E8=B7=AF=E4=B9=9F=EF=BC=8C=E5=88=A4?= =?UTF-8?q?=E5=AE=9A=E5=A5=BD=20Null=E3=80=81=E9=99=90=E5=88=B6=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/bo/EmailBusinessObject.java | 16 ----- .../config/security/SecurityConfig.java | 2 +- .../backend/controller/UserController.java | 44 +++++--------- .../request/user/PasswordResetDTO.java | 1 + .../request/user/PasswordResetVCodeDTO.java | 2 + .../request/user/PasswordUpdateDTO.java | 1 + .../controller/request/user/RegisterDTO.java | 5 +- ...DTO.java => SendRegistrationTokenDTO.java} | 2 +- .../request/user/UserInfoUpdateDTO.java | 4 +- .../maa/backend/service/EmailService.java | 45 +++++++------- .../plus/maa/backend/service/UserService.java | 58 ++++--------------- 11 files changed, 61 insertions(+), 119 deletions(-) rename src/main/java/plus/maa/backend/controller/request/user/{ResendActivateUrlDTO.java => SendRegistrationTokenDTO.java} (87%) diff --git a/src/main/java/plus/maa/backend/common/bo/EmailBusinessObject.java b/src/main/java/plus/maa/backend/common/bo/EmailBusinessObject.java index 57a9f51a..53094885 100644 --- a/src/main/java/plus/maa/backend/common/bo/EmailBusinessObject.java +++ b/src/main/java/plus/maa/backend/common/bo/EmailBusinessObject.java @@ -133,22 +133,6 @@ public void sendVerificationCodeMessage(String code) { } } - - public void sendActivateUrlMessage(String url) { - - try { - send(this.mailAccount, this.emailList - , this.title + " 账户激活" - , defaultMailIncludeHtmlTemplates( - "mail-activateUrl.ftlh", url - ) - , this.isHtml - ); - } catch (Exception ex) { - throw new RuntimeException("邮件发送失败", ex); - } - } - public void sendCommentNotification(Map map) { try { send(this.mailAccount, diff --git a/src/main/java/plus/maa/backend/config/security/SecurityConfig.java b/src/main/java/plus/maa/backend/config/security/SecurityConfig.java index cfa93a4a..0922176b 100644 --- a/src/main/java/plus/maa/backend/config/security/SecurityConfig.java +++ b/src/main/java/plus/maa/backend/config/security/SecurityConfig.java @@ -26,7 +26,7 @@ public class SecurityConfig { private static final String[] URL_WHITELIST = { "/user/login", "/user/register", - "/user/resendActivateUrl" + "/user/sendRegistrationToken" }; private static final String[] URL_PERMIT_ALL = { diff --git a/src/main/java/plus/maa/backend/controller/UserController.java b/src/main/java/plus/maa/backend/controller/UserController.java index 3787a167..0c91e9f8 100644 --- a/src/main/java/plus/maa/backend/controller/UserController.java +++ b/src/main/java/plus/maa/backend/controller/UserController.java @@ -5,25 +5,25 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import plus.maa.backend.config.SpringDocConfig; import plus.maa.backend.config.external.MaaCopilotProperties; import plus.maa.backend.config.security.AuthenticationHelper; import plus.maa.backend.controller.request.user.*; -import plus.maa.backend.controller.response.user.MaaLoginRsp; import plus.maa.backend.controller.response.MaaResult; +import plus.maa.backend.controller.response.user.MaaLoginRsp; import plus.maa.backend.controller.response.user.MaaUserInfo; import plus.maa.backend.service.EmailService; import plus.maa.backend.service.UserService; -import java.io.IOException; - /** * 用户相关接口 * register(@Parameter(description = "用户注册请 return MaaResult.success(userService.register(user)); } + /** + * 获得注册时的验证码 + */ + @PostMapping("/sendRegistrationToken") + @Operation(summary = "注册时发送验证码") + @ApiResponse(description = "发送验证码结果", responseCode = "204") + public MaaResult sendRegistrationToken(@Parameter(description = "发送注册验证码请求") @RequestBody @Valid SendRegistrationTokenDTO regDTO) { + userService.sendRegistrationToken(regDTO); + return new MaaResult<>(204, null, null); + } + /** * 用户登录 * @@ -149,27 +160,4 @@ public MaaResult register(@Parameter(description = "用户注册请 public MaaResult login(@Parameter(description = "登录请求") @RequestBody @Valid LoginDTO user) { return MaaResult.success("登陆成功", userService.login(user)); } - - @PostMapping("/resendActivateUrl") - @Operation(summary = "重新获取激活链接") - @ApiResponse(description = "发送激活链接") - public MaaResult resendActivateUrl(@Parameter(description = "重新获取激活链接请求") @RequestBody @Valid ResendActivateUrlDTO activateUrlDTO) { - userService.resendActivateUrl(activateUrlDTO); - return MaaResult.success(); - } - - @GetMapping("/activateAccount") - @Operation(summary = "激活账号") - @ApiResponse(description = "激活账号结果") - public MaaResult activateAccount(@Parameter(description = "激活请求") EmailActivateReq activateDTO, - @Parameter(description = "页面跳转参数") HttpServletResponse response) { - userService.activateAccount(activateDTO); - // 激活成功 跳转页面 - try { - response.sendRedirect(properties.getInfo().getFrontendDomain()); - } catch (IOException e) { - throw new RuntimeException(e); - } - return MaaResult.success(); - } } diff --git a/src/main/java/plus/maa/backend/controller/request/user/PasswordResetDTO.java b/src/main/java/plus/maa/backend/controller/request/user/PasswordResetDTO.java index 6e9f41f6..e0fdf9ad 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/PasswordResetDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/PasswordResetDTO.java @@ -18,6 +18,7 @@ public class PasswordResetDTO { /** * 邮箱 */ + @NotBlank(message = "邮箱格式错误") @Email(message = "邮箱格式错误") private String email; /** diff --git a/src/main/java/plus/maa/backend/controller/request/user/PasswordResetVCodeDTO.java b/src/main/java/plus/maa/backend/controller/request/user/PasswordResetVCodeDTO.java index 73afc856..4b843cd0 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/PasswordResetVCodeDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/PasswordResetVCodeDTO.java @@ -1,6 +1,7 @@ package plus.maa.backend.controller.request.user; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -17,6 +18,7 @@ public class PasswordResetVCodeDTO { /** * 邮箱 */ + @NotBlank(message = "邮箱格式错误") @Email(message = "邮箱格式错误") private String email; } diff --git a/src/main/java/plus/maa/backend/controller/request/user/PasswordUpdateDTO.java b/src/main/java/plus/maa/backend/controller/request/user/PasswordUpdateDTO.java index 15084688..8775b4c6 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/PasswordUpdateDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/PasswordUpdateDTO.java @@ -17,6 +17,7 @@ public class PasswordUpdateDTO { @NotBlank(message = "请输入原密码") private String originalPassword; + @NotBlank(message = "密码长度必须在8-32位之间") @Length(min = 8, max = 32, message = "密码长度必须在8-32位之间") private String newPassword; } diff --git a/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java b/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java index e65ccd42..4272cb9a 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/RegisterDTO.java @@ -19,9 +19,12 @@ public class RegisterDTO { @NotBlank(message = "邮箱格式错误") @Email(message = "邮箱格式错误") private String email; - @Length(min = 4, max = 24, message = "用户名长度应在2-24位之间") + @NotBlank(message = "用户名长度应在4-24位之间") + @Length(min = 4, max = 24, message = "用户名长度应在4-24位之间") private String userName; + @NotBlank(message = "密码长度必须在8-32位之间") @Length(min = 8, max = 32, message = "密码长度必须在8-32位之间") private String password; + @NotBlank(message = "请输入验证码") private String registrationToken; } diff --git a/src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java b/src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java similarity index 87% rename from src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java rename to src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java index 6acbf9d8..582271a4 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/ResendActivateUrlDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/SendRegistrationTokenDTO.java @@ -5,7 +5,7 @@ import lombok.Data; @Data -public class ResendActivateUrlDTO { +public class SendRegistrationTokenDTO { @NotBlank(message = "邮箱格式错误") @Email(message = "邮箱格式错误") private String email; diff --git a/src/main/java/plus/maa/backend/controller/request/user/UserInfoUpdateDTO.java b/src/main/java/plus/maa/backend/controller/request/user/UserInfoUpdateDTO.java index e7a6d622..2f03476a 100644 --- a/src/main/java/plus/maa/backend/controller/request/user/UserInfoUpdateDTO.java +++ b/src/main/java/plus/maa/backend/controller/request/user/UserInfoUpdateDTO.java @@ -1,5 +1,6 @@ package plus.maa.backend.controller.request.user; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -14,6 +15,7 @@ @NoArgsConstructor @AllArgsConstructor public class UserInfoUpdateDTO { - @Length(min = 4, max = 24, message = "用户名长度应在2-24位之间") + @NotBlank(message = "用户名长度应在4-24位之间") + @Length(min = 4, max = 24, message = "用户名长度应在4-24位之间") private String userName; } diff --git a/src/main/java/plus/maa/backend/service/EmailService.java b/src/main/java/plus/maa/backend/service/EmailService.java index 17e5d257..8d748bb5 100644 --- a/src/main/java/plus/maa/backend/service/EmailService.java +++ b/src/main/java/plus/maa/backend/service/EmailService.java @@ -2,11 +2,13 @@ import cn.hutool.extra.mail.MailAccount; import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import plus.maa.backend.common.bo.EmailBusinessObject; @@ -18,7 +20,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.UUID; /** * @author LoMu @@ -43,6 +44,11 @@ public class EmailService { private final MailAccount MAIL_ACCOUNT = new MailAccount(); + // 注入自身代理类的延迟加载代理类 + @Lazy + @Resource + private EmailService emailService; + /** * 初始化邮件账户信息 */ @@ -69,8 +75,18 @@ private void initMailAccount() { * @param email 邮箱 */ - @Async public void sendVCode(String email) { + // 限制请求间隔 + Integer interval = redisCache.getCache("HasBeenSentVCode:" + email, Integer.class); + if (interval != null) { + throw new MaaResultException(403, String.format("发送验证码的请求至少需要间隔 %d 秒", interval)); + } + // 调用注入的代理类执行异步任务 + emailService.asyncSendVCode(email); + } + + @Async + protected void asyncSendVCode(String email) { // 6位随机数验证码 String vcode = RandomStringUtils.random(6, true, true).toUpperCase(); if (flagNoSend) { @@ -84,6 +100,8 @@ public void sendVCode(String email) { } // 存redis redisCache.setCache("vCodeEmail:" + email, vcode, expire); + // 一个过期周期最多重发十条,记录已发送的邮箱以及间隔时间 + redisCache.setCache("HasBeenSentVCode:" + email, expire / 10, expire / 10); } /** @@ -98,29 +116,6 @@ public void verifyVCode(String email, String vcode) { } } - /** - * @param email 发送激活验证邮箱 - */ - @Async - public void sendActivateUrl(String email) { - // 生成uuid作为唯一标识符 - String uuid = UUID.randomUUID().toString().replaceAll("-", ""); - String url = domain + "/user/activateAccount?nonce=" + uuid; - if (flagNoSend) { - log.debug("url is " + url); - log.warn("Email not sent, no-send enabled"); - } else { - EmailBusinessObject.builder() - .setMailAccount(MAIL_ACCOUNT) - .setEmail(email) - .sendActivateUrlMessage(url); - } - // 存redis - redisCache.setCache("UUID:" + uuid, email, expire); - // 一个链接过期周期最多重发十条,记录已发送的邮箱以及间隔时间 - redisCache.setCache("HasBeenSentAU:" + email, expire / 10, expire / 10); - } - @Async public void sendCommentNotification(String email, CommentNotification commentNotification) { int limit = 25; diff --git a/src/main/java/plus/maa/backend/service/UserService.java b/src/main/java/plus/maa/backend/service/UserService.java index 8221ad93..764c55e4 100644 --- a/src/main/java/plus/maa/backend/service/UserService.java +++ b/src/main/java/plus/maa/backend/service/UserService.java @@ -1,6 +1,5 @@ package plus.maa.backend.service; -import cn.hutool.core.lang.Assert; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -39,7 +38,6 @@ public class UserService { // 未来转为配置项 private static final int LOGIN_LIMIT = 1; - private final RedisCache redisCache; private final UserRepository userRepository; private final EmailService emailService; private final PasswordEncoder passwordEncoder; @@ -109,9 +107,13 @@ public void modifyPassword(String userId, String rawPassword) { public MaaUserInfo register(RegisterDTO registerDTO) { String encode = passwordEncoder.encode(registerDTO.getPassword()); + // 校验验证码 + emailService.verifyVCode(registerDTO.getEmail(), registerDTO.getRegistrationToken()); + MaaUser user = new MaaUser(); BeanUtils.copyProperties(registerDTO, user); user.setPassword(encode); + user.setStatus(1); MaaUserInfo userInfo; try { MaaUser save = userRepository.save(user); @@ -119,8 +121,6 @@ public MaaUserInfo register(RegisterDTO registerDTO) { } catch (DuplicateKeyException e) { throw new MaaResultException(MaaStatusCode.MAA_USER_EXISTS); } - // 发送激活验证邮箱 - emailService.sendActivateUrl(registerDTO.getEmail()); return userInfo; } @@ -200,51 +200,17 @@ public void checkUserExistByEmail(String email) { } /** - * 重新发送激活邮件 + * 注册时发送验证码 */ - public void resendActivateUrl(ResendActivateUrlDTO activateUrlDTO) { - // 限制请求间隔 - Integer interval = redisCache.getCache("HasBeenSentAU:" + activateUrlDTO.getEmail(), Integer.class); - if (interval != null) { - throw new MaaResultException(403, String.format("发送激活邮件的请求至少需要间隔 %d 秒", interval)); - } + public void sendRegistrationToken(SendRegistrationTokenDTO regDTO) { // 判断用户是否存在 - MaaUser maaUser = userRepository.findByEmail(activateUrlDTO.getEmail()); - if (maaUser == null) { - throw new MaaResultException(MaaStatusCode.MAA_USER_NOT_FOUND); - } - // 禁止重复激活 - if (!Objects.equals(maaUser.getStatus(), 0)) { - throw new MaaResultException(403, "用户已经激活,无法再次发送验证码"); - } - // 重新发送验证码 - emailService.sendActivateUrl(activateUrlDTO.getEmail()); - } - - /** - * 激活账户 - * - * @param activateDTO uuid - */ - public void activateAccount(EmailActivateReq activateDTO) { - String uuid = activateDTO.getNonce(); - String email = redisCache.getCache("UUID:" + uuid, String.class); - Assert.notNull(email, "链接不存在或已过期"); - MaaUser user = userRepository.findByEmail(email); - - try { - if (Objects.equals(user.getStatus(), 1)) { - return; - } - // 激活账户 - user.setStatus(1); - userRepository.save(user); - - // 清除缓存 - } finally { - redisCache.removeCache("UUID:" + uuid); + MaaUser maaUser = userRepository.findByEmail(regDTO.getEmail()); + if (maaUser != null) { + // 用户已存在 + throw new MaaResultException(MaaStatusCode.MAA_USER_EXISTS); } + // 发送验证码 + emailService.sendVCode(regDTO.getEmail()); } - } From cda3cb10fcae38815f006e9ecafb6eda9bd59c2a Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sun, 17 Sep 2023 12:54:41 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=94=B9=E5=9B=9E=E7=BB=B4=E6=8C=81?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E6=80=9D=E8=B7=AF=EF=BC=8C=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E5=A5=BD=20Null=E3=80=81=E9=99=90=E5=88=B6=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/repository/UserRepository.java | 2 -- .../task/DeleteNotEnabledMaaUsersTask.java | 28 ------------------- 2 files changed, 30 deletions(-) delete mode 100644 src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java diff --git a/src/main/java/plus/maa/backend/repository/UserRepository.java b/src/main/java/plus/maa/backend/repository/UserRepository.java index f8dbaee5..35112d39 100644 --- a/src/main/java/plus/maa/backend/repository/UserRepository.java +++ b/src/main/java/plus/maa/backend/repository/UserRepository.java @@ -20,8 +20,6 @@ public interface UserRepository extends MongoRepository { */ MaaUser findByEmail(String email); - void deleteAllByStatusIs(Integer status); - default Map findByUsersId(List userId) { return findAllById(userId) .stream().collect(Collectors.toMap(MaaUser::getUserId, Function.identity())); diff --git a/src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java b/src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java deleted file mode 100644 index c66989e7..00000000 --- a/src/main/java/plus/maa/backend/task/DeleteNotEnabledMaaUsersTask.java +++ /dev/null @@ -1,28 +0,0 @@ -package plus.maa.backend.task; - -import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import plus.maa.backend.repository.UserRepository; - - -/** - * 定期删除未激活的账户,释放数据库空间 - * - * @author Lixuhuilll - * created on 2023.09.17 - */ -@Component -@RequiredArgsConstructor -public class DeleteNotEnabledMaaUsersTask { - - final UserRepository userRepository; - - /** - * 每三天的凌晨 0 点整清理一次 - */ - @Scheduled(cron = "0 0 0 */3 * ?") - public void deleteNotEnabledMaaUsers() { - userRepository.deleteAllByStatusIs(0); - } -} From ad6422dc468ed204df44b9bcee66b1901c915334 Mon Sep 17 00:00:00 2001 From: lixuhuilll <676824363@qq.com> Date: Sun, 17 Sep 2023 14:07:09 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E5=BE=AE=E8=B0=83=20RedisCache=20=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E9=99=90=E5=88=B6=E5=8F=91=E9=80=81=E9=97=B4=E9=9A=94?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../maa/backend/repository/RedisCache.java | 46 +++++++++++++++++++ .../maa/backend/service/EmailService.java | 11 ++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/main/java/plus/maa/backend/repository/RedisCache.java b/src/main/java/plus/maa/backend/repository/RedisCache.java index 249a6dbc..3b21b6e2 100644 --- a/src/main/java/plus/maa/backend/repository/RedisCache.java +++ b/src/main/java/plus/maa/backend/repository/RedisCache.java @@ -80,6 +80,52 @@ public void setCache(final String key, T value, long timeout, TimeUnit timeU } } + /** + * 当缓存不存在时,则 set + * + * @param key 缓存的 key + * @param value 被缓存的值 + * @return 是否 set + */ + + public boolean setCacheIfAbsent(final String key, T value) { + return setCacheIfAbsent(key, value, expire); + } + + /** + * 当缓存不存在时,则 set + * + * @param key 缓存的 key + * @param value 被缓存的值 + * @param timeout 过期时间 + * @return 是否 set + */ + + public boolean setCacheIfAbsent(final String key, T value, long timeout) { + return setCacheIfAbsent(key, value, timeout, TimeUnit.SECONDS); + } + + /** + * 当缓存不存在时,则 set + * + * @param key 缓存的 key + * @param value 被缓存的值 + * @param timeout 过期时间 + * @param timeUnit 过期时间的单位 + * @return 是否 set + */ + public boolean setCacheIfAbsent(final String key, T value, long timeout, TimeUnit timeUnit) { + String json = getJson(value); + if (json == null) return false; + boolean result; + if (timeout <= 0) { + result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, json)); + } else { + result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, json, timeout, timeUnit)); + } + return result; + } + public void addSet(final String key, Collection set, long timeout) { addSet(key, set, timeout, TimeUnit.SECONDS); } diff --git a/src/main/java/plus/maa/backend/service/EmailService.java b/src/main/java/plus/maa/backend/service/EmailService.java index 8d748bb5..b1a6ba5d 100644 --- a/src/main/java/plus/maa/backend/service/EmailService.java +++ b/src/main/java/plus/maa/backend/service/EmailService.java @@ -76,10 +76,11 @@ private void initMailAccount() { */ public void sendVCode(String email) { - // 限制请求间隔 - Integer interval = redisCache.getCache("HasBeenSentVCode:" + email, Integer.class); - if (interval != null) { - throw new MaaResultException(403, String.format("发送验证码的请求至少需要间隔 %d 秒", interval)); + // 一个过期周期最多重发十条,记录已发送的邮箱以及间隔时间 + final int timeout = expire / 10; + if (!redisCache.setCacheIfAbsent("HasBeenSentVCode:" + email , timeout, timeout)) { + // 设置失败,说明 key 已存在 + throw new MaaResultException(403, String.format("发送验证码的请求至少需要间隔 %d 秒", timeout)); } // 调用注入的代理类执行异步任务 emailService.asyncSendVCode(email); @@ -100,8 +101,6 @@ protected void asyncSendVCode(String email) { } // 存redis redisCache.setCache("vCodeEmail:" + email, vcode, expire); - // 一个过期周期最多重发十条,记录已发送的邮箱以及间隔时间 - redisCache.setCache("HasBeenSentVCode:" + email, expire / 10, expire / 10); } /**