From e0e733bc276dfad20932a8f64cf01e7f9e95a6af Mon Sep 17 00:00:00 2001 From: Ziin Date: Tue, 5 Aug 2025 15:16:03 +0800 Subject: [PATCH] =?UTF-8?q?1.=E7=94=A8=E6=88=B7=E9=82=AE=E7=AE=B1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=B3=A8=E5=86=8C=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/MyBatisCodeHelperDatasource.xml | 25 ++ pom.xml | 6 +- src/main/java/vvpkassistant/Application.java | 2 + .../User/controller/UserController.java | 14 ++ .../User/model/DTO/UserModelDTO.java | 2 - .../User/service/UserService.java | 2 + .../User/service/UserServiceImpl.java | 48 ++-- .../java/vvpkassistant/common/ErrorCode.java | 1 + .../config/SaTokenConfigure.java | 6 +- .../mail/service/MailService.java | 6 +- .../mail/service/MailServiceImpl.java | 220 +++++++++++++++--- src/main/resources/application.yml | 25 +- src/main/resources/mail.setting | 10 + target/classes/application.yml | 21 +- 14 files changed, 326 insertions(+), 62 deletions(-) create mode 100644 .idea/MyBatisCodeHelperDatasource.xml create mode 100644 src/main/resources/mail.setting diff --git a/.idea/MyBatisCodeHelperDatasource.xml b/.idea/MyBatisCodeHelperDatasource.xml new file mode 100644 index 0000000..5318661 --- /dev/null +++ b/.idea/MyBatisCodeHelperDatasource.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 50bce6a..ac84f77 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,11 @@ 5.8.38 - + + com.sun.mail + javax.mail + 1.6.2 + diff --git a/src/main/java/vvpkassistant/Application.java b/src/main/java/vvpkassistant/Application.java index 8336a0b..326f914 100644 --- a/src/main/java/vvpkassistant/Application.java +++ b/src/main/java/vvpkassistant/Application.java @@ -1,8 +1,10 @@ package vvpkassistant; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication +@EnableAsync public class Application { public static void main(String[] args) { diff --git a/src/main/java/vvpkassistant/User/controller/UserController.java b/src/main/java/vvpkassistant/User/controller/UserController.java index 7fd31c9..00d02ba 100644 --- a/src/main/java/vvpkassistant/User/controller/UserController.java +++ b/src/main/java/vvpkassistant/User/controller/UserController.java @@ -55,6 +55,9 @@ public class UserController { @Resource private UserService userService; + @Resource + private MailService mailService; + // 配置用户信息 @PostMapping("inputUserInfo") public ResponseData inputUserInfo(@RequestBody Map param) { @@ -371,4 +374,15 @@ public class UserController { public ResponseData mailRegister(@RequestBody UserModelDTO model){ return ResponseData.success(userService.addUserWithMail(model)); } + + @GetMapping("/activate") + public ResponseData activate(@RequestParam("token") String token){ + return ResponseData.success(userService.activateAccount(token)); + } + + @PostMapping("/resendMail") + public ResponseData resendMail(@RequestBody UserModelDTO userModelDTO){ + return ResponseData.success(mailService.resendMail(userModelDTO)); + } + } diff --git a/src/main/java/vvpkassistant/User/model/DTO/UserModelDTO.java b/src/main/java/vvpkassistant/User/model/DTO/UserModelDTO.java index 20a54bb..25b6e30 100644 --- a/src/main/java/vvpkassistant/User/model/DTO/UserModelDTO.java +++ b/src/main/java/vvpkassistant/User/model/DTO/UserModelDTO.java @@ -2,7 +2,6 @@ package vvpkassistant.User.model.DTO; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data @@ -24,5 +23,4 @@ public class UserModelDTO { private String newPassword; private String oldPassword; private String password; - } diff --git a/src/main/java/vvpkassistant/User/service/UserService.java b/src/main/java/vvpkassistant/User/service/UserService.java index 95a6448..497202a 100644 --- a/src/main/java/vvpkassistant/User/service/UserService.java +++ b/src/main/java/vvpkassistant/User/service/UserService.java @@ -17,4 +17,6 @@ public interface UserService extends IService { UserModelVO updateUserInfo(UserModelDTO userModelDTO); UserModelVO addUserWithMail(UserModelDTO model); + + Boolean activateAccount(String token); } diff --git a/src/main/java/vvpkassistant/User/service/UserServiceImpl.java b/src/main/java/vvpkassistant/User/service/UserServiceImpl.java index 5a44092..760be50 100644 --- a/src/main/java/vvpkassistant/User/service/UserServiceImpl.java +++ b/src/main/java/vvpkassistant/User/service/UserServiceImpl.java @@ -1,6 +1,7 @@ package vvpkassistant.User.service; import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.temp.SaTempUtil; import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -15,7 +16,7 @@ import vvpkassistant.User.model.UserModel; import vvpkassistant.User.model.UserModelVO; import vvpkassistant.common.ErrorCode; import vvpkassistant.exception.BusinessException; -import vvpkassistant.mail.model.MailModel; +import vvpkassistant.mail.service.MailService; import javax.annotation.Resource; @@ -31,6 +32,8 @@ public class UserServiceImpl extends ServiceImpl implements @Resource private WxChatParam wxChatParam; + @Autowired + private MailService mailService; @Override public UserModelVO loginWithMail(UserModelDTO model) { @@ -96,33 +99,52 @@ public class UserServiceImpl extends ServiceImpl implements } @Override - public UserModelVO addUserWithMail(UserModelDTO model) { + public UserModelVO addUserWithMail(UserModelDTO userModelDTO) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(UserModel::getEmail,model.getEmail()); + lambdaQueryWrapper.eq(UserModel::getEmail,userModelDTO.getEmail()); UserModel userModel = userDao.selectOne(lambdaQueryWrapper); if (userModel != null) { throw new BusinessException(ErrorCode.MAIL_ALREADY_EXIST); } - if (model.getPassword().length() < 6 ){ + if (userModelDTO.getPassword().length() < 6 ){ throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码长度不能小于 6 位"); } - model.setPassword(BcryptUtils.encryptPassword(model.getPassword())); - model.setCreateTime(VVTools.currentTimeStamp()); - //设置状态为正常 - model.setStatus(2); + userModelDTO.setPassword(BcryptUtils.encryptPassword(userModelDTO.getPassword())); + userModelDTO.setCreateTime(VVTools.currentTimeStamp()); + //设置状态为待验证 + userModelDTO.setStatus(2); //设置积分为0 - model.setPoints(0); - userDao.insert(BeanUtil.copyProperties(model, UserModel.class)); + userModelDTO.setPoints(0); + UserModel userModelEntity = BeanUtil.copyProperties(userModelDTO, UserModel.class); + if ( userDao.insert(userModelEntity) != 1){ + throw new BusinessException(ErrorCode.ADD_FAILED,"用户注册失败"); + } + mailService.sendMail(userModelDTO.getEmail(),userModelEntity.getId()); // 判断用户是否为邀请用户 - if (model.getInviterId() != null) { - UserModel oldUser = userDao.selectById(model.getInviterId()); + if (userModelDTO.getInviterId() != null) { + UserModel oldUser = userDao.selectById(userModelDTO.getInviterId()); oldUser.setPoints(oldUser.getPoints() + 10); userDao.updateById(oldUser); } - UserModelVO userModelVO = BeanUtil.copyProperties(model, UserModelVO.class); + UserModelVO userModelVO = BeanUtil.copyProperties(userModelEntity, UserModelVO.class); userModelVO.setNewAccount(true); userModelVO.setChatInfo(wxChatParam); return userModelVO; } + + @Override + public Boolean activateAccount(String token) { + Integer userId = SaTempUtil.parseToken(token, Integer.class); + UserModel userModel = userDao.selectById(userId); + if (userModel == null) { + throw new BusinessException(ErrorCode.USER_DOES_NOT_EXIST); + } + userModel.setStatus(0); + if (userDao.updateById(userModel) == 1){ + return true; + }else { + throw new BusinessException(ErrorCode.UPDATE_FAILED,"激活失败"); + } + } } diff --git a/src/main/java/vvpkassistant/common/ErrorCode.java b/src/main/java/vvpkassistant/common/ErrorCode.java index f55c51f..d6f23aa 100644 --- a/src/main/java/vvpkassistant/common/ErrorCode.java +++ b/src/main/java/vvpkassistant/common/ErrorCode.java @@ -15,6 +15,7 @@ public enum ErrorCode { UPDATE_FAILED(1003, "更新失败"), CONFIG_NAME_DUPLICATE(1004, "配置名称重复"), SIGN_IN_FAIL(1004, "当天已签到"), + EMAIL_SEND_FREQUENT(1005,"邮件发送太频繁,请 1 分钟后再试"), /* =============== 主播相关 =============== */ ANCHOR_ALREADY_EXISTS(2001, "主播已存在"), diff --git a/src/main/java/vvpkassistant/config/SaTokenConfigure.java b/src/main/java/vvpkassistant/config/SaTokenConfigure.java index 0ea7902..f984cff 100644 --- a/src/main/java/vvpkassistant/config/SaTokenConfigure.java +++ b/src/main/java/vvpkassistant/config/SaTokenConfigure.java @@ -31,6 +31,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { private String[] getExcludePaths() { return new String[]{ // Swagger & Knife4j 相关 + "/error", "/doc.html", "/webjars/**", "/swagger-resources/**", @@ -42,8 +43,9 @@ public class SaTokenConfigure implements WebMvcConfigurer { "/favicon.ico", // 你的其他放行路径,例如登录接口 "/user/loginWithPhoneNumber", - "/user/registerWithMail", - "/user/loginWithMail" + "/user/registerWithMail", + "/user/loginWithMail", + "/user/activate" }; } diff --git a/src/main/java/vvpkassistant/mail/service/MailService.java b/src/main/java/vvpkassistant/mail/service/MailService.java index 5319ee9..162f158 100644 --- a/src/main/java/vvpkassistant/mail/service/MailService.java +++ b/src/main/java/vvpkassistant/mail/service/MailService.java @@ -1,5 +1,6 @@ package vvpkassistant.mail.service; +import vvpkassistant.User.model.DTO.UserModelDTO; import vvpkassistant.mail.model.MailModel; /* @@ -7,5 +8,8 @@ import vvpkassistant.mail.model.MailModel; * @date: 2025/8/4 15:42 */ public interface MailService { - Boolean sendMail(MailModel model); + + void sendMail(String emailAddress,Integer userId); + + Boolean resendMail(UserModelDTO userModelDTO); } diff --git a/src/main/java/vvpkassistant/mail/service/MailServiceImpl.java b/src/main/java/vvpkassistant/mail/service/MailServiceImpl.java index 853212a..7978594 100644 --- a/src/main/java/vvpkassistant/mail/service/MailServiceImpl.java +++ b/src/main/java/vvpkassistant/mail/service/MailServiceImpl.java @@ -1,62 +1,208 @@ package vvpkassistant.mail.service; +import cn.dev33.satoken.temp.SaTempUtil; +import cn.hutool.extra.mail.MailUtil; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import com.mailgun.api.v3.MailgunMessagesApi; -import com.mailgun.client.MailgunClient; -import com.mailgun.model.message.Message; -import com.mailgun.model.message.MessageResponse; -import feign.AsyncClient; -import feign.Client; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import vvpkassistant.mail.model.MailModel; +import vvpkassistant.User.model.DTO.UserModelDTO; +import vvpkassistant.common.ErrorCode; +import vvpkassistant.exception.BusinessException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /* * @author: ziin * @date: 2025/8/4 15:42 */ +@Slf4j @Service public class MailServiceImpl implements MailService { - @Value("${mailgun}") - private String mailgunKey; - public final Cache codeCache = Caffeine.newBuilder() - .expireAfterWrite(10, TimeUnit.MINUTES) // 5 分钟过期 - .maximumSize(10_000) // 防止内存暴涨 + @Value("${activateUrl}") + private String activateUrl; + + private final Cache emailSendCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) .build(); @Override - public Boolean sendMail(MailModel model) { - ExecutorService executor = Executors.newFixedThreadPool(2); - AsyncClient.Default asyncClient = new AsyncClient.Default<>( - new Client.Default(null, null), executor); + @Async("taskExecutor") + public void sendMail(String emailAddress,Integer userId) { + log.info("Sending email to {}", emailAddress); + String token = SaTempUtil.createToken(userId, 600); + if (emailSendCache.getIfPresent(emailAddress) != null) { + throw new BusinessException(ErrorCode.EMAIL_SEND_FREQUENT); + } + MailUtil.send(emailAddress, "激活你的账号", "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Account Activation\n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + " \n" + + "

Account Activation

\n" + + "

Please click the button below to activate your account

\n" + + " \n" + + " \n" + + " Activate Account\n" + + " \n" + + " \n" + + "
\n" + + "
Important Notice:
\n" + + "
\n" + + " • This activation link is valid for 24 hours
\n" + + " • Please do not share this link with anyone
\n" + + " • If you didn't register an account, please ignore this message\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + " This is an automated message. Please do not reply to this email.\n" + + "
\n" + + "
\n" + + "\n" + + "\n", true); + emailSendCache.put(emailAddress, userId); + } - MailgunMessagesApi mailgunAsyncMessagesApi = MailgunClient.config(mailgunKey) - .client(asyncClient) - .createAsyncApi(MailgunMessagesApi.class); - - Message message = Message.builder() - .from("") - .to(model.getMailAddress()) - .subject("your mail address Verification code is : ") - .html("\n" + - "\n" + - "\t

Sending HTML emails with Mailgun

\n" + - "\t

Hello world

\n" + - "\t

More examples can be found here

\n" + - "\n" + - "") - .build(); - - MessageResponse messageResponse = mailgunAsyncMessagesApi.sendMessage("www.baidu.com", message); - return true; + @Override + public Boolean resendMail(UserModelDTO userModelDTO) { + try { + sendMail(userModelDTO.getEmail(), userModelDTO.getId()); + return true; + }catch (Exception e){ + throw new BusinessException(ErrorCode.SYSTEM_ERROR,e.getMessage()); + } } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9eb93ba..44a1dda 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,10 +4,25 @@ server: spring: profiles: active: local + task: + # Spring 执行器配置,对应 TaskExecutionProperties 配置类。对于 Spring 异步任务,会使用该执行器。 + execution: + thread-name-prefix: mail-task # 线程池的线程名的前缀。默认为 task- ,建议根据自己应用来设置 + pool: # 线程池相关 + core-size: 10 # 核心线程数,线程池创建时候初始化的线程数。默认为 8 。 + max-size: 20 # 最大线程数,线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUE + keep-alive: 60s # 允许线程的空闲时间,当超过了核心线程之外的线程,在空闲时间到达之后会被销毁。默认为 60 秒 + queue-capacity: 200 # 缓冲队列大小,用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。 + allow-core-thread-timeout: true # 是否允许核心线程超时,即开启线程池的动态增长和缩小。默认为 true 。 + shutdown: + await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true + await-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置 - -mailgun: b4888f65c7fd452213f3a8aea9d88269-812b35f5-006d7fd6 +mybatis-plus: + global-config: + db-config: + id-type: auto ############## Sa-Token 配置 (文档: https://sa-token.cc) ############## @@ -15,9 +30,9 @@ sa-token: # token 名称(同时也是 cookie 名称) token-name: token # token 有效期(单位:秒) 默认30天,-1 代表永久有效 - timeout: 2592000 + timeout: -1 # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 - active-timeout: -1 + active-timeout: 648000 # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) is-concurrent: true # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) @@ -26,3 +41,5 @@ sa-token: token-style: random-128 # 是否输出操作日志 is-log: true + +activateUrl: http://192.168.1.174:8086/user/activate?token= \ No newline at end of file diff --git a/src/main/resources/mail.setting b/src/main/resources/mail.setting new file mode 100644 index 0000000..7d89bf0 --- /dev/null +++ b/src/main/resources/mail.setting @@ -0,0 +1,10 @@ +# 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀> +host = smtp.exmail.qq.com +# 邮件服务器的SMTP端口,可选,默认25 +port = 465 +# 发件人(必须正确,否则发送失败) +from = chenfu@bilibili.so +# 用户名,默认为发件人邮箱前缀 +user = chenfu@bilibili.so +pass = Yt7rWTizdAvE4dc7 +sslEnable = true \ No newline at end of file diff --git a/target/classes/application.yml b/target/classes/application.yml index 9eb93ba..f69cce0 100644 --- a/target/classes/application.yml +++ b/target/classes/application.yml @@ -4,10 +4,25 @@ server: spring: profiles: active: local + task: + # Spring 执行器配置,对应 TaskExecutionProperties 配置类。对于 Spring 异步任务,会使用该执行器。 + execution: + thread-name-prefix: mail-task # 线程池的线程名的前缀。默认为 task- ,建议根据自己应用来设置 + pool: # 线程池相关 + core-size: 10 # 核心线程数,线程池创建时候初始化的线程数。默认为 8 。 + max-size: 20 # 最大线程数,线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUE + keep-alive: 60s # 允许线程的空闲时间,当超过了核心线程之外的线程,在空闲时间到达之后会被销毁。默认为 60 秒 + queue-capacity: 200 # 缓冲队列大小,用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。 + allow-core-thread-timeout: true # 是否允许核心线程超时,即开启线程池的动态增长和缩小。默认为 true 。 + shutdown: + await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true + await-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置 - -mailgun: b4888f65c7fd452213f3a8aea9d88269-812b35f5-006d7fd6 +mybatis-plus: + global-config: + db-config: + id-type: auto ############## Sa-Token 配置 (文档: https://sa-token.cc) ############## @@ -26,3 +41,5 @@ sa-token: token-style: random-128 # 是否输出操作日志 is-log: true + +activateUrl: http://192.168.1.174:8086/user/activate?token= \ No newline at end of file