1.用户修改邮箱逻辑

2.用户验证邮箱逻辑
This commit is contained in:
2025-08-05 15:39:38 +08:00
parent 200fd90339
commit 3106d787da
9 changed files with 201 additions and 4 deletions

1
.gitignore vendored
View File

@@ -27,3 +27,4 @@ replay_pid*
/.idea/
/target/

Binary file not shown.

View File

@@ -375,8 +375,8 @@ public class UserController {
return ResponseData.success(userService.addUserWithMail(model));
}
@GetMapping("/activate")
public ResponseData<Object> activate(@RequestParam("token") String token){
@GetMapping("/activateAccount")
public ResponseData<Object> activateAccount(@RequestParam("token") String token){
return ResponseData.success(userService.activateAccount(token));
}
@@ -385,4 +385,8 @@ public class UserController {
return ResponseData.success(mailService.resendMail(userModelDTO));
}
@GetMapping("/verificationMail")
public ResponseData<Object> verificationMail(@RequestParam("token") String token){
return ResponseData.success(userService.verificationMail(token));
}
}

View File

@@ -22,5 +22,5 @@ public class UserModel {
private Integer inviterId; // 邀请人id
private String email;
private String password;
private Integer mailVerification;
}

View File

@@ -19,4 +19,6 @@ public interface UserService extends IService<UserModel> {
UserModelVO addUserWithMail(UserModelDTO model);
Boolean activateAccount(String token);
Boolean verificationMail(String token);
}

View File

@@ -75,6 +75,10 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements
userModelDTO.setPassword(BcryptUtils.encryptPassword(userModelDTO.getNewPassword()));
}
}
if (userModelDTO.getEmail() != null) {
mailService.sendVerificationMail(userModelDTO.getEmail(), userModelDTO.getId());
}
// 用户有密码的情况下重新设置密码
if (userInfo.getPassword() != null && userModelDTO.getOldPassword() != null) {
if (BcryptUtils.matchPassword(userModelDTO.getOldPassword(), userInfo.getPassword())) {
@@ -117,6 +121,7 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements
//设置积分为0
userModelDTO.setPoints(0);
UserModel userModelEntity = BeanUtil.copyProperties(userModelDTO, UserModel.class);
userModelEntity.setMailVerification(1);
if ( userDao.insert(userModelEntity) != 1){
throw new BusinessException(ErrorCode.ADD_FAILED,"用户注册失败");
}
@@ -141,10 +146,22 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements
throw new BusinessException(ErrorCode.USER_DOES_NOT_EXIST);
}
userModel.setStatus(0);
userModel.setMailVerification(0);
if (userDao.updateById(userModel) == 1){
return true;
}else {
throw new BusinessException(ErrorCode.UPDATE_FAILED,"激活失败");
}
}
@Override
public Boolean verificationMail(String token) {
Integer userId = SaTempUtil.parseToken(token, Integer.class);
UserModel userModel = userDao.selectById(userId);
userModel.setMailVerification(0);
if (userDao.updateById(userModel) == 1){
return true;
}
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"邮箱验证失败");
}
}

View File

@@ -11,5 +11,9 @@ public interface MailService {
void sendMail(String emailAddress,Integer userId);
void sendVerificationMail(String emailAddress,Integer userId);
Boolean resendMail(UserModelDTO userModelDTO);
}

View File

@@ -26,6 +26,10 @@ public class MailServiceImpl implements MailService {
@Value("${activateUrl}")
private String activateUrl;
@Value("${verificationMailUrl}")
private String verificationMailUrl;
private final Cache<String, Object> emailSendCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
@@ -196,6 +200,170 @@ public class MailServiceImpl implements MailService {
emailSendCache.put(emailAddress, userId);
}
@Override
public void sendVerificationMail(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, "验证你的邮箱", "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
" <title>Account Activation</title>\n" +
" <style>\n" +
" * {\n" +
" margin: 0;\n" +
" padding: 0;\n" +
" box-sizing: border-box;\n" +
" }\n" +
"\n" +
" body {\n" +
" font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n" +
" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n" +
" min-height: 100vh;\n" +
" display: flex;\n" +
" align-items: center;\n" +
" justify-content: center;\n" +
" padding: 20px;\n" +
" }\n" +
"\n" +
" .container {\n" +
" background: white;\n" +
" border-radius: 12px;\n" +
" box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);\n" +
" padding: 40px;\n" +
" text-align: center;\n" +
" max-width: 400px;\n" +
" width: 100%;\n" +
" }\n" +
"\n" +
" .icon {\n" +
" width: 60px;\n" +
" height: 60px;\n" +
" background: #4CAF50;\n" +
" border-radius: 50%;\n" +
" display: flex;\n" +
" align-items: center;\n" +
" justify-content: center;\n" +
" margin: 0 auto 20px;\n" +
" color: white;\n" +
" font-size: 24px;\n" +
" }\n" +
"\n" +
" h1 {\n" +
" color: #333;\n" +
" font-size: 24px;\n" +
" margin-bottom: 10px;\n" +
" font-weight: 600;\n" +
" }\n" +
"\n" +
" .subtitle {\n" +
" color: #666;\n" +
" font-size: 16px;\n" +
" margin-bottom: 30px;\n" +
" line-height: 1.5;\n" +
" }\n" +
"\n" +
" .activate-button {\n" +
" display: inline-block;\n" +
" background: #4CAF50;\n" +
" border: none;\n" +
" border-radius: 8px;\n" +
" padding: 15px 30px;\n" +
" margin: 20px 0;\n" +
" font-size: 18px;\n" +
" font-weight: bold;\n" +
" color: white;\n" +
" text-decoration: none;\n" +
" cursor: pointer;\n" +
" transition: all 0.3s ease;\n" +
" box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n" +
" }\n" +
" \n" +
" .activate-button:hover {\n" +
" background: #45a049;\n" +
" transform: translateY(-2px);\n" +
" box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);\n" +
" }\n" +
" \n" +
" .activate-button:active {\n" +
" transform: translateY(0);\n" +
" box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n" +
" }\n" +
"\n" +
" .info {\n" +
" background: #e3f2fd;\n" +
" border-left: 4px solid #2196F3;\n" +
" padding: 15px;\n" +
" margin: 20px 0;\n" +
" text-align: left;\n" +
" border-radius: 4px;\n" +
" }\n" +
"\n" +
" .info-title {\n" +
" font-weight: 600;\n" +
" color: #1976D2;\n" +
" margin-bottom: 5px;\n" +
" }\n" +
"\n" +
" .info-text {\n" +
" color: #424242;\n" +
" font-size: 14px;\n" +
" line-height: 1.4;\n" +
" }\n" +
"\n" +
" .footer {\n" +
" margin-top: 30px;\n" +
" padding-top: 20px;\n" +
" border-top: 1px solid #eee;\n" +
" color: #999;\n" +
" font-size: 12px;\n" +
" }\n" +
"\n" +
" @media (max-width: 480px) {\n" +
" .container {\n" +
" padding: 30px 20px;\n" +
" }\n" +
" \n" +
" .verification-code {\n" +
" font-size: 24px;\n" +
" letter-spacing: 2px;\n" +
" }\n" +
" }\n" +
" </style>\n" +
"</head>\n" +
"<body>\n" +
" <div class=\"container\">\n" +
" <div class=\"icon\">✉</div>\n" +
" \n" +
" <h1>Verification Mail</h1>\n" +
" <p class=\"subtitle\">Please click the button below to verification your mail</p>\n" +
" \n" +
" <a href=\""+ verificationMailUrl+ token + "\" class=\"activate-button\">\n" +
" Verification\n" +
" </a>\n" +
" \n" +
" <div class=\"info\">\n" +
" <div class=\"info-title\">Important Notice:</div>\n" +
" <div class=\"info-text\">\n" +
" • This activation link is valid for 10 minutes<br>\n" +
" • Please do not share this link with anyone<br>\n" +
" • If you didn't register an account, please ignore this message\n" +
" </div>\n" +
" </div>\n" +
" \n" +
" <div class=\"footer\">\n" +
" This is an automated message. Please do not reply to this email.\n" +
" </div>\n" +
" </div>\n" +
"</body>\n" +
"</html>\n", true);
emailSendCache.put(emailAddress, userId);
}
@Override
public Boolean resendMail(UserModelDTO userModelDTO) {
try {

View File

@@ -42,4 +42,5 @@ sa-token:
# 是否输出操作日志
is-log: true
activateUrl: http://192.168.1.174:8086/user/activate?token=
activateUrl: http://192.168.1.174:8086/user/activate?token=
verificationMailUrl: http://192.168.1.174:8086/user/verification?token=