1.发送邮件
分三步:
- 邮箱设置
- Spring Email
- 模板引擎
1. 邮件设置
- 邮箱的设置
首先我们进入第一个环节:邮箱的设置
这里我们使用qq邮箱(其他的邮箱也是一样的)
开启 POP3/SMTP 服务去发送邮件
2. Spring Email如何使用
- 怎么去使用 Spring Email
接下来是第二步,怎么去使用 Spring Email
首先需要引入 spring mail 依赖,可以去maven中心仓库搜索
之后将依赖粘贴到项目的 pom.xml 配置文件中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.7.1</version>
</dependency>
要用这个邮箱,肯定得把邮箱的账号、密码、链接、协议等都配好。如果将来想换一个邮箱,只要改配置文件就好了,不要把邮箱的参数写死在 java 程序里。
进行邮箱的配置是在 application.properties 配置文件中配置的。
# MailProperties
# 声明访问邮箱的域名是什么(下一行中后面的是 qq 浏览器的smtp服务器的域名)
spring.mail.host=smtp.qq.com
# 配置端口(几乎所有的邮箱默认的端口都是465)
spring.mail.port=465
# 配置用户名和授权码
spring.mail.username=958691367@qq.com
spring.mail.password=lpfejerqpiqxbeda
# 配置协议(启用安全加密的的smtp协议)
spring.mail.protocol=smtps
# 发送邮件的时候采用 ssl 安全连接方式
spring.mail.properties.mail.smtp.ssl.enable=true
- 使用 JavaMailSender 接口 发送邮件
为了是发送邮件这个功能可以复用,我们写一个工具把整个的流程封装一下。
这个类它要把发邮件的事委托给 qq 去做,它相当于代替了 qq 邮箱的客户端,或者说它就是一个客户端。
MailClient类(工具类):
@Component
public class MailClient {
private Logger logger = LoggerFactory.getLogger(MailClient.class);
@Autowired
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String from;
public void sendMail(String to, String subject, String content){
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
mailSender.send(helper.getMimeMessage());
} catch (MessagingException e) {
logger.error("发送邮件失败" + e.getMessage());
}
}
}
3. 测试
1. 发送普通文本
在发送的时候要在邮箱中要生成授权码复制授权码
然后将 application.properties 配置文件中的 mail 邮箱密码设置为 刚刚复制的授权码,之后就可以测试发送邮件了。
@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {
@Autowired
private MailClient mailClient;
@Test
public void testTextMail(){
String to = "958691367@qq.com";
String subject = "Test2";
String content = "Welcome.";
mailClient.sendMail(to, subject, content);
}
}
上面程序一执行程序就通过协议访问了 qq 的邮件服务器,把这些信息提交给了服务器,让服务器帮我们把这些东西发送给对方。
2. 利用 thymeleaf 引擎 去发送 HTML 格式的邮件
上面发送的只是普通的文本,接下来我们看怎么利用 thymeleaf 引擎 去发送 HTML 格式的邮件
先创建一个 HTML 文件,然后将第二行修改使其成为 themeleaf 模板
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.themeleaf.org">
<head>
<meta charset="UTF-8">
<title>邮件示例</title>
</head>
<body>
<p>欢迎你,<span style="color: red;" th:text="${username}"></span>!</p>
</body>
</html>
测试类
测试第二个方法 “发送HTML”
@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
@Test
public void testTextMail(){
String to = "958691367@qq.com";
String subject = "Test4";
String content = "Welcome.";
mailClient.sendMail(to, subject, content);
}
@Test
public void testHtmlMail(){
Context context = new Context();
context.setVariable("username", "sunday");
String content = templateEngine.process("/mail/demo", context);
System.out.println(content);
mailClient.sendMail("958691367@qq.com", "HTML", content);
}
}
themeleaf模板引擎 有一个核心的类,这个类也是被 Spring 管理起来的,需要的话直接注入进来就可以用。
测试结果
2. 开发注册功能
这种相对复杂的功能我们要学会拆解,使得每一部分都相对简单一点就好开发了。对于一个 web 功能我们可以按照请求去拆解,因为每一个功能都是由若干个浏览器和服务器之间的请求交互所构成的。
分析得知,开发注册功能由三个请求构成
- 访问注册页面
- 提交注册页面,服务器发送激活邮件
- 点击邮件中的链接激活服务
我们一次请求一次请求去写,相对来说会比较容易。
每次请求按照 数据访问层 --> 业务层 --> 视图层的顺序开发
但是注意有些功能可能会没有数据访问层,有些功能没有业务层。
1. 访问注册页面
首先写controller
@Controller
public class LoginController {
@RequestMapping(path = "/register", method = RequestMethod.GET)
public String getRegisterPage(){
return "/site/register";
}
}
然后修改 themeleaf 资源 register.html
首先将第二行修改为下面这句使其声明为 themeleaf 模板
<html lang="en" xmlns:th="http://www.thymeleaf.org">
然后是修改开头的css文件和末尾的js文件或者图片的路径,绝对路径不用管,主要是相对路径要进行一个处理,将其交给 themeleaf 去管理。(注意:themeleaf 模板资源都是以 templates 目录为根路径的)
然后我们访问注册页面是通过点击首页的 “注册” 超链接点过来的,接下来我们来将首页 index.html 的 “注册” 超链接写一下。
每一个页面头部的header区域都是一样的,如果每个页面都复制处理一下会非常的麻烦, 所以最好的方式就是让header这段代码在多个页面复用。header的复用 themeleaf 也是支持的。
# 什么是header
header标签定义文档的页眉。语法格式为“<header>内容</header>”。header元素是一种具有引导和导航作用的结
构元素,通常用来放置整个页面或页面内的一个内容区块的标题等。
# themeleaf如何复用header
模板中,经常希望从其他模板中包含?些部分,如?眉,?脚,公共菜单等部分,为了做到这?点,Thymeleaf 可以使
?th:fragment 属性来定义被包含的模版?段,以供其他模版包含,其他页面可以通过 th:replace 来使用。
给 index.html 的header起个名字
register.html 复用 index.html 的header
接下来启动项目看一下是否开发成功
经测试,访问注册页面成功!
2. 提交注册数据
前置操作
在开发之初再导入一个包,这个包主要提供一些判断字符串、集合等常用的一些数据空值的情况。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
然后我们需要在 application.properties 里将网站的域名配置好,当然现在没有域名,配的是本机的IP地址,因为在注册的过程中要发邮件,邮件里面得带激活链接,这个链接得链到我们的网站,所以我们需要有一个链接。因为这个链接在开发阶段在测试阶段在上线的时候它是不一样的,所以我们需要把它做成可配的。
# community 下面的key即“community”也是我们自己取的,声明本机域名,当然现在我们也没有域名,我们写本机的ip地址
community.path.domain=http://localhost:8080
然后我们还需要写一个工具类,在工具类里面提供两个方法,后面注册的时候好用,不然注册的时候现写有点麻烦。这个工具类里面的是静态方法,这个工具类就不交给容器管理了。
public class CommunityUtil {
public static String generateUUID(){
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String md5(String key){
if(StringUtils.isBlank(key)){
return null;
}
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
接下来就正式开发提交注册数据了
因为注册是针对用户的,所以我们可以把逻辑写到我们之前创建的 UserService 之中。
# 分析
注册时要发邮件,所以我们要把 邮件客户端(MailClient) 、模板引擎注入进来。
发邮件的时候要生成激活码,激活码当中要包含域名、项目名,所以我们也要把配置文件中的域名和项目名
都注入进来。
开发 service
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPath;
public User findUserById(int id){
return userMapper.selectById(id);
}
public Map<String, Object> register(User user){
Map<String, Object> map = new HashMap<>();
if(user == null){
throw new IllegalArgumentException("参数不能为空");
}
if(StringUtils.isBlank(user.getUsername())){
map.put("usernameMsg", "账号不能为空!");
return map;
}
if(StringUtils.isBlank(user.getPassword())){
map.put("passwordMsg", "密码不能为空!");
return map;
}
if(StringUtils.isBlank(user.getEmail())){
map.put("emailMsg", "邮箱不能为空!");
return map;
}
User u = userMapper.selectByName(user.getUsername());
if(u != null){
map.put("usernameMsg", "该账号已存在!");
return map;
}
u = userMapper.selectByEmail(user.getEmail());
if(u != null){
map.put("emailMsg", "该邮箱已被注册!");
return map;
}
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
user.setType(0);
user.setStatus(0);
user.setActivationCode(CommunityUtil.generateUUID());
user.setHeaderUrl(String.format("https://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
user.setCreateTime(new Date());
userMapper.insertUser(user);
Context context = new Context();
context.setVariable("email", user.getEmail());
String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
url = url.replaceAll(" ", "");
context.setVariable("url", url);
String content = templateEngine.process("/mail/activation", context);
System.out.println(user.getEmail());
try {
mailClient.sendMail(user.getEmail(), "激活账号", content);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
发送给用户的 themeleaf 模板 activation.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<title>牛客网-激活账号</title>
</head>
<body>
<div>
<p>
<b th:text="${email}">xxx@xxx.com</b>, 您好!
</p>
<p>
您正在注册牛客网, 这是一封激活邮件, 请点击
<a th:href="${url}">此链接</a>,
激活您的牛客账号!
</p>
</div>
</body>
</html>
接下来开发 controller,controller的话在之前的 LoginController 中写可以
@Controller
public class LoginController {
@Autowired
private UserService userService;
@RequestMapping(path = "/register", method = RequestMethod.GET)
public String getRegisterPage(){
return "/site/register";
}
@RequestMapping(path = "/register", method = RequestMethod.POST)
public String register(Model model, User user){
Map<String, Object> map = userService.register(user);
if(map == null || map.isEmpty()){
model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
model.addAttribute("target", "/index");
return "/site/operate-result";
} else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
model.addAttribute("emailMsg", map.get("emailMsg"));
return "/site/register";
}
}
}
operate-result.html 操作成功之后的页面
注意:在 themeleaf 模板中
路径用 @
controller 传过来的参数接收用 $
还需要对 register.html 模板做一个处理
因为 register.html 之前路径、header 已经做过了一次处理,所以这次主要是对内容做一个处理,内容部分其实主要就是对表单的处理。
表单提交到哪去
什么样的提交方式
另外,如果注册失败会返回一些错误信息,在 register.html 页面显示,所以我们还要对错误信息进行一些处理。
注意:这里确认密码和密码不一致是在前端处理的,所以我们后台无需处理。
注:上面表单的提交方式和调教路径写错位置了,应该写在 标签中,而不是 < div> 标签中
注:上面的 th:text="${emailMsg}" 少加了右边的双引号
然后这里我们可以测试一下
3. 激活注册账号
最后我们来开发 “激活注册账号” 的功能
开发顺序 数据访问层(dao) --> 业务层(service) --> 视图层(controller 和 themeleaf 模板)
因为之前已经写过了 dao 中的方法,所以我们接下来应该写的是 业务层(service)
激活有三种结果:
成功
重复激活
激活失败(激活码错误)
我们把这三种结果封装到常量接口里,好能够复用,得到状态。
public interface CommunityConstant {
int ACTIVATION_SUCCESS = 0;
int ACTIVATION_REPEAT = 1;
int ACTIVATION_FAILURE = 2;
}
在UserService里面去处理激活逻辑的时候,需要用到上面的接口中的常量。
所以我们让UserService去实现上面的接口CommunityConstant
然后我们在UserService写激活的方法,这个返回激活的状态,成功还是失败,int
在激活的时候,服务器会传给后台 用户id 和 激活码,所以激活方法要求参数为 userId 和 激活码
注:为了UserService的完整性,所以也把与激活账号无关的代码粘了进来,与激活账号有关的部分没就两点:1. 实现 CommunityConstant 接口 2. activation方法
@Service
public class UserService implements CommunityConstant {
@Autowired
private UserMapper userMapper;
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPath;
public User findUserById(int id){
return userMapper.selectById(id);
}
public Map<String, Object> register(User user){
Map<String, Object> map = new HashMap<>();
if(user == null){
throw new IllegalArgumentException("参数不能为空");
}
if(StringUtils.isBlank(user.getUsername())){
map.put("usernameMsg", "账号不能为空!");
return map;
}
if(StringUtils.isBlank(user.getPassword())){
map.put("passwordMsg", "密码不能为空!");
return map;
}
if(StringUtils.isBlank(user.getEmail())){
map.put("emailMsg", "邮箱不能为空!");
return map;
}
User u = userMapper.selectByName(user.getUsername());
if(u != null){
map.put("usernameMsg", "该账号已存在!");
return map;
}
u = userMapper.selectByEmail(user.getEmail());
if(u != null){
map.put("emailMsg", "该邮箱已被注册!");
return map;
}
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
user.setType(0);
user.setStatus(0);
user.setActivationCode(CommunityUtil.generateUUID());
user.setHeaderUrl(String.format("https://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
user.setCreateTime(new Date());
userMapper.insertUser(user);
Context context = new Context();
context.setVariable("email", user.getEmail());
String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
url = url.replaceAll(" ", "");
context.setVariable("url", url);
String content = templateEngine.process("/mail/activation", context);
System.out.println(user.getEmail());
try {
mailClient.sendMail(user.getEmail(), "激活账号", content);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
public int activation(int userId, String code){
User user = userMapper.selectById(userId);
if(user.getStatus() == 1){
return ACTIVATION_REPEAT;
} else if(user.getActivationCode().equals(code)){
userMapper.updateStatus(userId, 1);
return ACTIVATION_SUCCESS;
} else {
return ACTIVATION_FAILURE;
}
}
}
然后我们需要在controller里面把请求处理一下
收到的邮件就是html,那个页面我们之前已经做好了。
然后我们在 LoginController 中 再加一个方法处理请求。
我们还需要处理的是登录页面
还需要给 login.html 声明为 themeleaf 模板,然后处理一下 login.html 样式文件的路径就可以了
最后还要修改一下 首页index.html 的登录按钮,使其可以使用
最后我们再处理一下登录页面 login.html 的验证码
3. 会话管理
Cookie和Session各有各的优点和缺点
1. Cookie
Cokie诞生原因
HTTP 是无状态的:在同一个连接中,两个执行成功的请求之间是没有关系的。这就带来了一个问题,用户没有办法在同一个网站中进行连续的交互,比如在一个电商网站里,用户把某个商品加入到购物车,切换一个页面后再次添加了商品,这两次添加商品的请求之间没有关联,浏览器无法知道用户最终选择了哪些商品。而使用 HTTP 的头部扩展,HTTP Cookies 就可以解决这个问题。把 Cookies 添加到头部中,创建一个会话让每次请求都能共享相同的上下文信息,达成相同的状态。
注意,HTTP 本质是无状态的,使用 Cookies 可以创建有状态的会话。
# HTTP Cookie
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会
在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏
览器,如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。
Cookie应用场景
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
Cookie 曾一度用于客户端数据的存储,因当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB 。
cookie优缺点
优点:可以解决HTTP 无状态的问题
缺点:Cookie存在在客户端,可能会造成隐私泄露;
? 另外浏览器每次发送给服务器Cookie会对流量产生影响
当浏览器发送cookie给服务器时,服务器接收cookie以后可能取里面的值用一下,或者在模板里用一下。
演示cookie
通过上面的文字,相信我们对 Cookie 已经有了简单的认识,下面写几个简单的小例子体会一下相关语法。
因为只是简单的小例子,不是什么业务,所以写到 AlphaController里,这里装着一些演示的小demo。
@Controller
@RequestMapping("/alpha")
public class AlphaController {
@RequestMapping(path = "/cookie/set", method = RequestMethod.GET)
@ResponseBody
public String setCookie(HttpServletResponse response){
Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());
cookie.setPath("/community/alpha");
cookie.setMaxAge(60 * 10);
response.addCookie(cookie);
return "set cookie";
}
@RequestMapping(path = "/cookie/get", method = RequestMethod.GET)
@ResponseBody
public String getCookie(@CookieValue("code") String code){
System.out.println(code);
return "get cookie";
}
}
测试:
首先浏览器访问Controller的cookie/set方法,可以看到请求里面没有cookie,但是响应里面有cookie
然后浏览器访问Controller的cookie/get方法可以看到请求里面也有了cookie
2. Session
Sesson定义
是JavaEE的标准,用于在服务端记录客户端信息。
注:Session本质是依赖于cookie的,浏览器和服务器之间是多对一的关系,浏览器和Session之间的对应关系怎么找是依赖于cookie的(将sessionId存到cookie里发送)。
Session 优缺点
优点:
? 可以解决http无状态的问题
? 数据存放在服务器更加安全
缺点:
? 数据存放在服务器会增加服务器的压力
演示Session
@Controller
@RequestMapping("/alpha")
public class AlphaController {
@RequestMapping(path = "/session/set", method = RequestMethod.GET)
@ResponseBody
public String setSession(HttpSession session){
session.setAttribute("id", 1);
session.setAttribute("name", "Test");
return "set session";
}
@RequestMapping(path = "/session/get", method = RequestMethod.GET)
@ResponseBody
public String getSession(HttpSession session){
System.out.println(session.getAttribute("id"));
System.out.println(session.getAttribute("name"));
return "get session";
}
}
3. 分布式使用Session存在什么问题,解决方案是什么
访问分布式中之前没访问过的服务器时,服务器里面没有存在之前的session,浏览器得不到之前的session
- 解决:黏性session,对于一个固定ip的请求,固定分给一个服务器处理。
缺点:分布式本来是解决负载均衡问题的,这种解决方法会导致负载不均衡问题。
- 解决:同步session,某一个服务器创建session并且存了数据之后,它会把这个session同步给其他的服务器
缺点:这种方法会对服务器性能产生一定的影响;服务器和服务器之间有了一定的关系,产生耦合,之间不是那 么的独立了。
- 解决:共享session,搞一台共享服务器,这个服务器不是处理业务的,而是专门用来存session的,
其他服务器都向这台服务器获取session, 缺点:如果共享session服务器宕机了,其他服务器都没办法工作了。
目前解决方案,能存到cookie就存到cookie,不能存到cookie的数据就存到关系型数据库里,数据库的数据存在硬盘里,存取比较慢,性能慢。但是存到 非关系型数据库NoSQL比如redis就快很多了
我们在前面开发的时候会话数据先存到数据库或者session(不适合存到mysql里的先存到session)里,在后面学习了redis之后的再迁移到redis里
4. 生成验证码
https://code.google.com/archive/p/kaptcha
1. 导入 jar 包
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. 编写 Kaptcha 配置类
因为SpringBoot没有整合Kaptcha ,所以我们要自己写配置类将Kaptcha注入Spring容器
@Configuration
public class KaptchaConfig {
@Bean
public Producer kaptchaProducer(){
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width", "100");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "32");
properties.setProperty("kaptcha.textproducer.font.color", "black");
properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
DefaultKaptcha kaptcha = new DefaultKaptcha();
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
3. 测试生成验证码的方法
不是在LoginController中的getLoginPage访问登录方法之中写的,getLoginPage这个方法是给浏览器返回一个html,而这个html里面会包含一个图片的路径,浏览器会依据路径再次访问服务器获得这个图片,所以我们需要再单独写一个请求(LoginController中的方法)向浏览器返回图片,当然登录页面的html会引用这个方法的路径。
@Controller
public class LoginController implements CommunityConstant {
private static Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
private UserService userService;
@Autowired
private Producer kaptchaProducer;
@RequestMapping(path = "/register", method = RequestMethod.GET)
public String getRegisterPage(){
return "/site/register";
}
@RequestMapping(path = "/register", method = RequestMethod.POST)
public String register(Model model, User user){
Map<String, Object> map = userService.register(user);
if(map == null || map.isEmpty()){
model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
model.addAttribute("target", "/index");
return "/site/operate-result";
} else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
model.addAttribute("emailMsg", map.get("emailMsg"));
return "/site/register";
}
}
@RequestMapping(path = "activation/{userId}/{code}", method = RequestMethod.GET)
public String activation(Model model,
@PathVariable("userId") int userId,
@PathVariable("code") String code){
int result = userService.activation(userId, code);
if(result == ACTIVATION_SUCCESS){
model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
model.addAttribute("target", "/login");
} else if(result == ACTIVATION_REPEAT){
model.addAttribute("msg", "无效操作,该账号已经激活过了!");
model.addAttribute("target", "/index");
} else {
model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
model.addAttribute("target", "/index");
}
return "/site/operate-result";
}
@RequestMapping(path = "/login", method = RequestMethod.GET)
public String getLoginPage(){
return "/site/login";
}
@RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
public void getLKaptcha(HttpServletResponse response, HttpSession session){
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);
session.setAttribute("kaptcha", text);
response.setContentType("image/png");
try {
ServletOutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error("响应验证码失败:" + e.getMessage());
}
}
}
# 为什么方法的返回值是 void 呢?
因为我们向浏览器输出的是特殊的东西,是一个图片,不是一个字符串,也不是一个网页,所以这个方法比较特别,我们需要自己用 response 对象手动的向浏览器输出,所以方法的返回值为 void 。
# 将验证码存入session
生成验证码之后,服务端要把它记住,浏览器登陆的时候好验证验证码对不对,又因为它是敏感数据,所以我们将它存
到 session里。
4. 将生成验证码的方法应用到登录页面上
<script>
function refresh_kaptcha() {
var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random();
$("#kaptcha").attr("src", path);
}
</script>
测试:
点击 “刷新验证码”
|