Spring boot 论坛项目实战_02
1.发送邮件
邮箱设置
启用客户端SMTP服务
Spring Email【集成在 Spring 中】
导入 jar 包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <version>2.1.5.RELEASE</version> </dependency>邮箱参数配置
用配置文件,完成邮箱参数配置,不要一次性写死,便于后续更新
Application.properties 中配置
# MailProperties
# 使用的邮箱域名
spring.mail.host=smtp.sina.com
spring.mail.port=465
spring.mail.username= 自己的邮箱用户名
spring.mail.password= 自己的邮箱的 SMTP 授权码
# 指定协议
spring.mail.protocol=smtps
# 其他详尽配置
# 发送邮件的时候采用安全的 ssl 连接
spring.mail.properties.mail.smtp.ssl.enable=true使用 JavaMailSender[Spring 的 Bean] 发送邮件
如果出现: 身份认证错误[ 下面这种 ], 记得检查邮箱配置的 username 和 password
org.springframework.mail.MailAuthenticationException: Authentication failed;
注意: 用 STMP 发送的文件, 在发送邮箱本身的已发送是看不到的.
模板引擎
使用Thymeleaf 发送 HTML 邮件
2.开发注册功能
-
访问注册页面
-
点击顶部区域内的链接,打开注册页面
-
-
-
提交注册数据
-
通过表单提交数据
-
服务端验证帐号是否已存在、邮箱是否已注册
-
-
-
这里注意下,一定要保证数据库里的数据唯一性,不然会报错
-
-
服务端发送激活邮件
-
-
激活注册帐号
-
点击邮件中的链接,访问服务端的激活服务
-
-
3.会话管理
-
HTTP的基本性质
-
HTTP是简单的
-
HTTP是可扩展的
-
HTTP是无状态的,有会话的
-
使用 Cookies 可以创建有状态的会话
-
-
-
Cookie【分布式,尽量用Cookie保存数据】
-
是服务器发送到浏览器,并保存在浏览器端的一小块数据。
-
浏览器下次访问该服务器时,会自动携带该块数据,并将其发送给服务器
-
// Cookie 示例 @RequestMapping(path = "/cookie/set", method = RequestMethod.GET) @ResponseBody public String setCookie(HttpServletResponse response) { // 创建Cookie Cookie cookie = new Cookie("code", CommunityUtil.generateUUID()); // 设置 Cookie 生效范围 cookie.setPath("/community/alpha"); // 设置 Cookie 生存时间, 默认 关闭浏览器就消失 cookie.setMaxAge(60 * 10);// 单位秒 // 发送 cookie 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"; }
-
-
Session【分布式中不方便存 Cookie 的数据,可以存在 NoSql 数据库中:Redis】
-
是 JavaEE 的标准【API】,用于在服务端记录客户端信息
-
数据存放在服务端更加安全,但是也会增加服务端的内存压力
-
// Session 示例 @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"; }
-
4. 生成验证码
-
Kaptcha
-
导入jar包
-
编写Kaptcha 配置类
-
设置图片 长、宽、字符数、干扰细节
-
-
-
生成随机字符,生成图片
package com.nowcoder.community.config; import com.google.code.kaptcha.Producer; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; /** * Kaptcha 的配置类 */ @Configuration public class KaptchaConfig { @Bean public Producer kaptchaProducer(){ Properties properties = new Properties(); // 设置 kaptcha 的属性 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","0,0,0");// R,G,B properties.setProperty("kaptcha.textproducer.char.string","1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 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; } }
#JS问题 <!-- 原本刷新验证码方法失效 --> <a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码</a> <script> function refresh_kaptcha() { var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random(); $("#kaptcha").attr('src', path); } </script> <!-- 解决办法: 用点击实现验证码图片的 src 更新, 添加随机参数保证图片的刷新 --> <a href="javascript:refresh_kaptcha();" onclick="changeImage()" class="font-size-12 align-bottom">刷新验证码</a> <script> function changeImage(){ var element = document.getElementById('kaptcha'); var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random(); element.src = path; } </script>
5.开发登录、退出功能
-
访问登录页面
-
点击顶部区域内的链接,打开登录页面
-
-
登录
-
验证帐号、密码、验证码
-
成功时,生成登录凭证,发放给客户端
-
失败时,跳转回登录页
-
-
退出
-
将登陆凭证更新为失效状态
-
跳转至网站首页
-
6. 显示登录信息
-
拦截器示例
-
定义拦截器,实现 HandlerInterceptor 接口
-
配置拦截器,为它指定拦截、排除的路径
-
/** * 拦截器的配置类 * implements WebMvcConfigurer * 实现这个接口 */ @Configuration public class WebMvcConfig implements WebMvcConfigurer { // 注入拦截器对象 @Autowired private AlphaInterceptor alphaInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 添加要注册的拦截器; // 指定[排除]拦截路径, 若不指定则是拦截所有资源; // 明确要拦截的路径 registry.addInterceptor(alphaInterceptor) /* 排除对静态资源的拦截*/ .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg") /*指定要拦截的 路径*/ .addPathPatterns("/register","/login"); } }
-
实现:
-
写一个类实现接口,根据实际情况写方法
-
配置拦截器,指定它拦截的请求
-
-
优势:降低代码耦合
-
-
-
拦截器应用
-
在请求开始时,查询登录用户
-
// 在Controller之前执行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 具体处理 logger.debug("preHandle: " + handler.toString()); return true; }
-
-
在本次请求中持有用户数据
-
// 利用 ThreadLocal 保证多线程的并发安全: // 以线程[CurrentThread]为 Map 的key 来存取值
-
-
在模板视图上显示用户数据
-
// 在Controller之后执行 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 具体处理 logger.debug("postHandle: " + handler.toString()); }
-
-
在请求结束时清理用户数据
-
// 在TemplateEngine之后执行 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 具体处理 logger.debug("afterCompletion: " + handler.toString()); }
-
-
7.账号设置
-
上传文件
-
请求:必须是POST 请求
-
表单:添加属性 : enctype= “multipart/form-data”
-
Spring MVC:通过MultipartFile 处理上传文件
-
注意需要先把上传路径的文件夹建好
-
-
开发步骤
-
访问帐号设置页面
-
上传头像
-
获取头像
-
8.检查登陆状态
-
使用拦截器
-
在方法前标注自定义注解
-
拦截所有请求,只处理带有该注解的方法
-
-
自定义注解
-
注解的类型是:
-
常用的元注解:
-
// 定义自己声明的注解,可以用在哪个位置 @Target // 声明自定义注解,保留或有效时间 @Retention // 【下面两个非必须】 // 声明自定义注解,在生成文档的时候要不要带上这个注解 @Document // 用于继承,子类在继承父类时,要不要继承这个自定义注解 @Inherited
-
-
如何读取注解:
-
// 通过反射读取, 调用注解 // 获取所有注解 Method.getDeclaredAnnotations(); // 按照类型获取这个类型的注解 Method.getAnnotation(Class<T> annotationClass);
-
-
注解的使用:
-
/* * 这个注解的位置 : 方法上面 * 这个注解的有效期: 程序运行时 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginRequired { // 这个注解就只起一个标识的作用 // 声明这个方法,要在程序运行后才能访问 }
-
-