1.用户邮箱登录注册接口实现

This commit is contained in:
2025-08-05 15:16:03 +08:00
parent f5cbe5cac2
commit e0e733bc27
14 changed files with 326 additions and 62 deletions

View File

@@ -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) {

View File

@@ -55,6 +55,9 @@ public class UserController {
@Resource
private UserService userService;
@Resource
private MailService mailService;
// 配置用户信息
@PostMapping("inputUserInfo")
public ResponseData<Object> inputUserInfo(@RequestBody Map<String,Object> param) {
@@ -371,4 +374,15 @@ public class UserController {
public ResponseData<Object> mailRegister(@RequestBody UserModelDTO model){
return ResponseData.success(userService.addUserWithMail(model));
}
@GetMapping("/activate")
public ResponseData<Object> activate(@RequestParam("token") String token){
return ResponseData.success(userService.activateAccount(token));
}
@PostMapping("/resendMail")
public ResponseData<Object> resendMail(@RequestBody UserModelDTO userModelDTO){
return ResponseData.success(mailService.resendMail(userModelDTO));
}
}

View File

@@ -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;
}

View File

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

View File

@@ -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<UserDao, UserModel> implements
@Resource
private WxChatParam wxChatParam;
@Autowired
private MailService mailService;
@Override
public UserModelVO loginWithMail(UserModelDTO model) {
@@ -96,33 +99,52 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements
}
@Override
public UserModelVO addUserWithMail(UserModelDTO model) {
public UserModelVO addUserWithMail(UserModelDTO userModelDTO) {
LambdaQueryWrapper<UserModel> 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,"激活失败");
}
}
}

View File

@@ -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, "主播已存在"),

View File

@@ -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"
};
}

View File

@@ -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);
}

View File

@@ -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<String, String> codeCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 5 分钟过期
.maximumSize(10_000) // 防止内存暴涨
@Value("${activateUrl}")
private String activateUrl;
private final Cache<String, Object> emailSendCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
@Override
public Boolean sendMail(MailModel model) {
ExecutorService executor = Executors.newFixedThreadPool(2);
AsyncClient.Default<Object> 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, "激活你的账号", "<!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>Account Activation</h1>\n" +
" <p class=\"subtitle\">Please click the button below to activate your account</p>\n" +
" \n" +
" <a href=\""+ activateUrl+ token + "\" class=\"activate-button\">\n" +
" Activate Account\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 24 hours<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);
}
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("<html>\n" +
"<body>\n" +
"\t<h1>Sending HTML emails with Mailgun</h1>\n" +
"\t<p style=\"color:blue; font-size:30px;\">Hello world</p>\n" +
"\t<p style=\"font-size:30px;\">More examples can be found <a href=\"https://documentation.mailgun.com/en/latest/api-sending.html#examples\">here</a></p>\n" +
"</body>\n" +
"</html>")
.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());
}
}
}