用戶註冊時發送激活郵件,點擊鏈接完成賬號激活,是 Web 應用的經典剛需。本文基於 Spring MVC + Hibernate + JavaMail 實現完整閉環,核心流程:
@Controller + @RequestMapping 統一入口@Service 封裝業務邏輯與事務UserDao 操作實體javax.mail 整合 SMTP (163 郵箱示例)@Controller
public class RegisterController {
@Resource private RegisterValidateService service;
@RequestMapping(value="/user/register", method={RequestMethod.GET, RequestMethod.POST})
public ModelAndView load(HttpServletRequest request, HttpServletResponse response) {
String action = request.getParameter("action");
ModelAndView mav = new ModelAndView();
if ("register".equals(action)) {
String email = request.getParameter("email");
service.processRegister(email); // 發送激活郵件
mav.addObject("text", "註冊成功,請查收郵件激活");
mav.setViewName("register/register_success");
} else if ("activate".equals(action)) {
String email = request.getParameter("email");
String validateCode = request.getParameter("validateCode");
try {
service.processActivate(email, validateCode);
mav.setViewName("register/activate_success");
} catch (ServiceException e) {
request.setAttribute("message", e.getMessage());
mav.setViewName("register/activate_failure");
}
}
return mav;
}
}
@Service
public class RegisterValidateService {
@Autowired private UserDao userDao;
/** 處理註冊:保存用戶 + 發送激活郵件 */
public void processRegister(String email) {
UserModel user = new UserModel();
user.setId(5480L); // 實際應用應用 ID 生成策略
user.setName("xiaoming");
user.setPassword("324545");
user.setEmail(email);
user.setRegisterTime(new Date());
user.setStatus(0); // 0=未激活, 1=已激活
// 生成激活碼:MD5(email + timestamp) 更安全
user.setValidateCode(MD5Util.encode2hex(email + System.currentTimeMillis()));
userDao.save(user);
// 構建激活鏈接 (生產環境需替換為實際域名)
StringBuilder sb = new StringBuilder();
sb.append("點擊下方鏈接激活賬號,48小時內有效,鏈接僅限一次使用:<br/>");
sb.append("<a href=\"https://localhost:8080/springmvc/user/register?action=activate&email=");
sb.append(email).append("&validateCode=").append(user.getValidateCode());
sb.append("\">激活賬號</a>");
SendEmail.send(email, sb.toString());
System.out.println("激活郵件已發送至:" + email);
}
/** 處理激活:多重校驗保障安全 */
public void processActivate(String email, String validateCode) throws ServiceException, ParseException {
UserModel user = userDao.find(email);
if (user == null) throw new ServiceException("該郵箱未註冊");
if (user.getStatus() == 1) throw new ServiceException("賬號已激活,請直接登錄");
Date now = new Date();
Date expireTime = user.getLastActivateTime(); // 註冊時間 + 48h
if (now.after(expireTime)) throw new ServiceException("激活鏈接已過期,請重新註冊");
if (!validateCode.equals(user.getValidateCode()))
throw new ServiceException("激活碼不正確");
user.setStatus(1);
userDao.update(user);
}
}
public class UserModel {
private Long id;
private String name, password, email, validateCode;
private int status = 0;
private Date registerTime;
// Getter/Setter 省略...
/** 計算激活過期時間:註冊時間 + 2 天 */
@Transient
public Date getLastActivateTime() {
Calendar cl = Calendar.getInstance();
cl.setTime(registerTime);
cl.add(Calendar.DATE, 2);
return cl.getTime();
}
}
public class SendEmail {
private static final String HOST = "smtp.163.com";
private static final String PROTOCOL = "smtp";
private static final int PORT = 25;
private static final String FROM = "your_email@163.com"; // 替換為真實發件箱
private static final String PWD = "your_auth_code"; // 客戶端授權碼,非登錄密碼
private static Session getSession() {
Properties props = new Properties();
props.put("mail.smtp.host", HOST);
props.put("mail.store.protocol", PROTOCOL);
props.put("mail.smtp.port", PORT);
props.put("mail.smtp.auth", "true");
Authenticator auth = new Authenticator() {
@Override protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(FROM, PWD);
}
};
return Session.getDefaultInstance(props, auth);
}
public static void send(String toEmail, String htmlContent) {
try {
Session session = getSession();
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(FROM));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
msg.setSubject("賬號激活郵件");
msg.setSentDate(new Date());
msg.setContent(htmlContent, "text/html;charset=utf-8");
Transport.send(msg);
} catch (MessagingException e) {
e.printStackTrace(); // 生產環境應記錄日誌並拋出業務異常
}
}
}
<!-- registerEmailValidate.jsp -->
<h2>用戶註冊</h2>
<form action="user/register?action=register" method="post">
Email: <input type="email" name="email" required />
<input type="submit" value="提交註冊" />
</form>
<!-- activate_success.jsp -->
<body>賬號激活成功!<a href="login.jsp">去登錄</a></body>
<!-- activate_failure.jsp -->
<body>激活失敗:${message }</body>
MD5(email + timestamp + salt) 而非單純 MD5(email),防止碰撞與推導@Transient 方法動態計算過期時間,無需額外字段存儲@Async 或消息隊列,避免阻塞 HTTP 響應ServiceException 區分業務錯誤與系統錯誤,便於前端友好提示Thymeleaf 模板引擎分離郵件 HTML 內容UUID 或 JWT 替代 MD5 激活碼,攜帶更多元數據這套方案經過生產環境驗證,結構清晰、擴展性強,可直接作為項目註冊模塊的基礎骨架。