第2章 Spring Boot实践,开发社区登录模块

 @Override
    //在模版执行后清除user数据
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        hostHolder.clear();
    }

.1发送邮件

 登陆注册需要向用户发送邮件,验证。

SpringBoot集成了发送邮件的功能。这个方便多了

模版引擎帮助发送邮件携带连接等功能,HTML邮件

 开启POP3/SMTP功能:

 导入Spring mail jar包--Spring Boot Mail Starter

配置邮箱参数-访问连接,协议,换邮箱改配置,不写死。

#MailProperties
spring.mail.host=smtp.qq.com
spring.mail.password=888888888
spring.mail.port=465
spring.mail.username=888888888@qq.com
#协议,安全
spring.mail.protocol=smtps
#ssl安全连接,加密
spring.mail.properties.mail.smtp.ssl.enable = true

QQ邮箱要用授权码,不是密码;

 

 

 发送邮件:激活、忘记密码----HTML邮件---thymeleaf邮件

详细配置和使用:

springboot发送邮件的几种方式 - 圣圣¥ - 博客园

封装逻辑,方便复用。

编写MailClient类,充当客户端,帮助自己发邮件

@Component
public class MailClient {
    private static final 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) {//对象 主题  正文
        MimeMessage message = mailSender.createMimeMessage();//一个帮助类
        MimeMessageHelper helper = null;//帮助构建message内容
        try {
            helper = new MimeMessageHelper(message, true);
            helper.setFrom("3329597457@qq.com");
            helper.setTo("3329597457@qq.com");
            helper.setSubject(subject);
            helper.setText(content,true);//支持html文件
            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            logger.error("发送邮件失败:"+e.getMessage());
        }
    }
}

测试:

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTest {
    @Autowired
    private MailClient mailClient;
    @Autowired
    private TemplateEngine templateEngine;
    @Test
    public void testMailSend() {
        String context = "<p>hello 大家好,这是一封测试邮件,这封邮件包含两种图片,分别如下</p><p>第一张图片:</p><img src='cid:p01'/><p>第二张图片:</p><img src='cid:p02'/>";
        mailClient.sendMail("3329597457@qq.com","测试邮件",context);
    }
    @Test
    public void testHtmlMail(){
        Context context = new Context();//模版引擎的内容
        context.setVariable("username","sunday");//传入参数
        String process = templateEngine.process("/mail/demo", context);//将内容放到模版中,处理完是字符串
        System.out.println(process);
        mailClient.sendMail("3329597457@qq.com","HTML测试邮件",process);
    }

2.2开发注册功能

 功能复杂,需要拆解,每个部分简单,按照请求拆解,因为功能一般由多个请求和响应组成。

点击链接跳到登陆界面---在首页点击链接

 异常:This application has no explicit mapping for /error, so you are seeing this as a fallback. - 萌新啊萌新是我 - 博客园

@Controller
public class LoginController {
    @RequestMapping(path = "/register",method = RequestMethod.GET)
    public String getRegisterPage(){//返回登陆界面
        return "/site/register.html";
    }
}

 Common - lang3  判断字符串数据空值的情况。

网站域名配置,激活连接,链接到网站,该链接在开发和上线是是不同的,需要可配置。

工具类:注册方法 生成随机字符串  ,MD5加密 上传头像。。。

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 User findUserByName(String username){
        return userMapper.selectByName(username);
    }
    public User findUserByEmail(String email){
        return userMapper.selectByEmail(email);
    }

    //注册
    public Map<String,Object> register(User user){//注册用户,返回一个Map封装的结果,注册就是添加新的用户
        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()));//用户密码+随机字符串,,md5加密
        user.setType(0);//
        user.setStatus(0);//没有激活
        user.setActivationCode(CommunityUtil.generateUUID());//设置激活码
        user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png",new Random().nextInt(1000)));//随机头像连接
        user.setCreateTime(new Date());

        //放入数据库
        userMapper.insertUser(user);
        //激活邮件 模版 activation.html
        Context context = new Context();
        context.setVariable("email",user.getEmail());
        //http://localhost:8080/community/activate/101/code
        //域名 + 项目名 + 功能的访问名 + use id + 激活码
        //插入用户后,数据库给用户分配id
        String url = domain + contextPath + "/activation/" + user.getId() + "/" +user.getActivationCode();
        context.setVariable("url",url);
        //熏染模版
        String content = templateEngine.process("/mail/activation",context);
        mailClient.sendMail(user.getEmail(),"激活",content);
        return map;//map==null说明没有问题
    }
}

 点击邮箱链接激活,就是修改用户的数据库中状态字段。

激活:三种结果:成功,重复激活,激活失败--定义常量,放在util--CommunityConstant

2.3会话管理

https://www.ieft.org

MDN Web Docs     火狐浏览器--支持中文

HTTP | MDN -- HTTP相关概述、教程

HTTP是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础,是一种 client-server 协议,也就是说,请求通常是由像浏览器这样的接受方发起的。一个完整的Web文档通常是由不同的子文档拼接而成的,像是文本、布局描述、图片、视频、脚本等等。

HTTP 的基本性质

HTTP 是简单的

虽然下一代HTTP/2协议将HTTP消息封装到了帧(frames)中,HTTP大体上还是被设计得简单易读。HTTP报文能够被人读懂,还允许简单测试,降低了门槛,对新人很友好。

HTTP 是可扩展的

在 HTTP/1.0 中出现的 HTTP headers 让协议扩展变得非常容易。只要服务端和客户端就新 headers 达成语义一致,新功能就可以被轻松加入进来。

HTTP 是无状态,有会话的

HTTP是无状态的:在同一个连接中,两个执行成功的请求之间是没有关系的。这就带来了一个问题,用户没有办法在同一个网站中进行连续的交互,比如在一个电商网站里,用户把某个商品加入到购物车,切换一个页面后再次添加了商品,这两次添加商品的请求之间没有关联,浏览器无法知道用户最终选择了哪些商品。而使用HTTP的头部扩展,HTTP Cookies就可以解决这个问题。把Cookies添加到头部中,创建一个会话让每次请求都能共享相同的上下文信息,达成相同的状态。

注意,HTTP本质是无状态的,使用Cookies可以创建有状态的会话。

浏览器请求,服务器响应,并在响应头中放cookie信息,浏览器下次访问时,在请求头中携带cookie信息。

cookie:

创建cookie ->设置cookie设置cookie生效范围->cookie生存时间 -> 发送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";
    }
//Set-Cookie
	//code=d4fdd804a8a84567bbf9c43e199be500; Max-Age=600; Expires=Tue, 14-Sep-2021 12:08:22 //GMT; Path=/community/alpha
//服务器创建一个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有关联,session将用户信息(密码等)存放在服务器,但是当用户和浏览器是多对一,服务器识别用户并建立与其对应session的关联需要用户的cookie

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

浏览器第一次访问服务器,服务器为期创建session (SpringMVC会自动创建,当我们创建后 HttpSession Session),并在cookie中存放sessionId,浏览器下次访问需要携带cookie,携带其sessionID

一台服务器,session随便用,但是多台,尤其是分布式,session用的比较少,为什么?

如图,当一台浏览器访问分布式集群,负载均衡可能将其分配给服务器1,并创建session;

然后以后访问,由在服务器3中创建session。资源浪费

解决:

黏性筛选;同一个Id只分给同一个服务器。--导致负载不均衡

同步筛选:服务器会把session会同步到其他,---影响服务器性能,服务器发生关联耦合影响分布式部署

共享筛选:搞一个服务器,只存放session,其他服务器从其中取session---该独苗挂了就凉了--瓶颈--

---不存session,用cookie,敏感信息放在数据库,数据库做集群相当ok,但是关系型数据库将数据放在硬盘,但是影响速度,---存到菲关系型数据库---redis---放在内存中

2.4验证码

Kaptcha是什么?

kaptcha 是谷歌开源的非常实用的验证码生成工具,基于SimpleCaptcha的开源项目。使用Kaptcha 生成验证码十分简单并且参数可以进行自定义。只需添加jar包配置下就可以使用,通过配置,可以自己定义验证码大小、颜色、显示的字符等等。下面就来讲一下如何使用kaptcha生成验证码以及在服务器端取出验证码进行校验。

Kaptch  --导入jar包--编写Kaptcha配置类(图片多长多宽,几个字,颜色干扰有没有类型)---生产随机字符、生产图片

验证码放在登陆页面,写在哪呢?

 导入jar包

编写配置类:KaptchaConfig  用@Bean注解,放入容器。Properties对象用来放置配置参数

实例化一个 DefaultKaptcha对象,实例化一个Config对象,将Properties导入Config,设置KaptchaConfig 对象参数

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.char.length","4");
        properties.setProperty("kaptcha.textproducer.char.space","5");
        properties.setProperty("kaptcha.textproducer.font.size","32");
        properties.setProperty("kaptcha.textproducer.font.color","0,0,0");
        properties.setProperty("kaptcha.textproducer.char.string","0123456789ABCDEFGHIJKLMNOPQISTUVWXYZ");
        properties.setProperty("kaptcha.noise.imple","DefaultNoise");//采用哪个干扰类

        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Config config = new Config(properties);
        kaptcha.setConfig(config);
        return kaptcha;
    }
}
//登陆
    @RequestMapping(path = "/login",method = RequestMethod.GET)
    public String getLoginPage(){
        return "/site/login";
    }

 上述代码返回登陆的Html文件,文件中要包含验证码图片链接,需要浏览器再次访问服务器获得图片,需要单独写一个请求向浏览器请求图片。请求会在模版中应用其路径。

生产随机字符和对应验证码,将验证码图片通过流操作响应到页面。

//生产验证码
    @RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response , HttpSession session){
        String text = kaptchaProducer.createText();//生随机字符串
        BufferedImage image = kaptchaProducer.createImage(text);//生产验证码
        //将验证码放入session
        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());
        }
    }

 修改登陆界面中验证码图片链接为动态,设置刷新验证码。

2.配置kaptcha

以项目使用spring为例,配置一个默认的Kaptcha的bean,如下

 

	<bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha" scope="singleton">
	    <property name="config">
	        <bean class="com.google.code.kaptcha.util.Config">
	            <constructor-arg>
	                <props>
	                	<prop key="kaptcha.session.key">kaptcha.code</prop>  
	                	<!-- 无边框 -->
	                    <prop key="kaptcha.border">no</prop>
	                    <prop key="kaptcha.textproducer.font.color">black</prop>
	                    <!-- 渲染效果:水纹:WaterRipple;鱼眼:FishEyeGimpy;阴影:ShadowGimpy -->
	                    <prop key="kaptcha.obscurificator.impl">com.google.code.kaptcha.impl.WaterRipple</prop>
	                    <!-- 不要噪点 -->
	                    <prop key="kaptcha.noise.impl">com.google.code.kaptcha.impl.NoNoise</prop>
	                    <prop key="kaptcha.image.width">90</prop>
	                    <prop key="kaptcha.image.height">33</prop>
	                    <prop key="kaptcha.textproducer.font.size">25</prop>
	                    <prop key="kaptcha.textproducer.char.length">4</prop>
	                    <prop key="kaptcha.textproducer.char.space">5</prop>
	                    <!-- 和登录框背景颜色一致 -->
	                    <prop key="kaptcha.background.clear.from">247,247,247</prop>
	                    <prop key="kaptcha.background.clear.to">247,247,247</prop>
	                </props>
	            </constructor-arg>
	        </bean>
	    </property>
	</bean>

2.5登陆、退出

 登陆业务逻辑:用户在登陆页面填写数据,像服务器提交信息,服务器验证数据,ok-->,生成一个凭证(Cookie)响应给客户端,记录登陆的状态,方便用户登陆状态在多个请求中连续。

验证数据--密码账号验证码

成功--> Cookie  敏感信息放在session

失败,跳转回首页。

验证信息表:ticket--主要信息,一个随机字符串,status验证信息状态,0-有效/1-无效;expired--过期时间

数据访问---业务---控制

用户登录,验证成功,往表中添加一条LoginTicket,存放服务器,服务器响应客服端,将表中ticket放入cookie发送浏览器,浏览器下次请求携带此ticket(cookie),同数据库中ticket比较,也定登录验证状态。

----编写实体列LoginTicket(就是Cookie&session)

----编写Mapper接口---在Mapper接口中添加注解(插入,按照ticket查询,按照ticket更新status)

----service  结果放在map,涉及空值处理,验证账号,验证激活状态,验证密码(MD5),生产登陆凭证(Cookie  string),登陆成功,将登陆的凭证放在map,响应给浏览器,浏览器下次请求放在请求头

----controller 登陆 ,检查验证码 Session中的验证码和提交的验证码是否一样,从session中获取验证码,和提交验证码比较,错误信息放入map;登陆验证是否有真确凭证,如果返回map有ticket,登陆成功,所以将ticket放入cookie,设置cookie作用路径和生命周期响应(response.addCookie(cookie);)给浏览器,重定向到主页,验证登陆失败,将map中失败信息放到model,回到登陆页面

前端处理:

通过model可以获得,springMVC会自动将实体对象(user)放入model,前端可以通过model得到数据,但是对于普通的参数,Spring 不会将其放入model,---》两种方法:1、人为放入model,2、参数可以通过请求(Request)获得

param.password --》requset.get对象的参数
<div class="form-group row mt-4">
						<label for="password" class="col-sm-2 col-form-label text-right">密码:</label>
						<div class="col-sm-10">
							<input type="password" th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
                                   
								   th:value="${param.password}"
								   id="password" placeholder="请输入您的密码!" required>
							<div class="invalid-feedback" th:text="${passwordMsg}">
								密码长度不能小于8位!
							</div>							
						</div>
					</div>

退出:将登陆信息修改为失效状态,跳转到首页

传入凭证ticket,将ticket状态改为1--无效

//退出登陆
    @RequestMapping(path = "/loyout",method = RequestMethod.GET)
    public String layout(@CookieValue("ticket") String ticket){
        userService.layout(ticket);
        return "redirect:/login";
    }

 servlet--调用dao方法ticket对音状态该1

controller--t通过spring的cookie注解将cookie中的ticket注入,调用servlet,修改状态,跳转登陆

拓展:

开发忘记密码的功能:

- 点击登录页面上的“忘记密码”链接,打开忘记密码页面。

- 在表单中输入注册的邮箱,点击获取验证码按钮,服务器为该邮箱发送一份验证码。

- 在表单中填写收到的验证码及新密码,点击重置密码,服务器对密码进行修改。

2.6显示登陆信息

 显示登陆信息,但是每个请求页面都有这个静态页面,如果每个请求都要处理登陆信息显示,耦合度太高,不利于后期维护。

拦截器可以拦击浏览器访问过来的请求信息甚至全部,拦截到请求后,可以在每个请求的头或者尾添加代码,实现对请求共有业务的批量处理

AlphaInterceptor 实现了 HandlerInterceptor接口,其中HandlerInterceptor有三个默认方法,

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

Controller前执行

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

Controller后执行 

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
在TemplateEngine之后执行

为了让spring知道拦截那些请求,需要添加config类,(之前config类的主要目的是声明一个第三方的bean),该config需要实现一个WebMvcConfigurer接口。

 用户信息显示流程:

浏览器发送请求,服务器得到请求头中cookie中的ticket,根据ticket查询login_ticket表得到用户id,根据用户id得到用户信息,用户名,头像连接。。。放到model熏染到模版Template.

每次请求都要做的事。

用拦截器实现:

1/在请求一开始就获得请求的ticket(preHandle);   封装一个工具类,从request中获取cookie,从cookie中获取ticket,根据ticket查表得到登陆状态消息,如果状态信息有效,根据userId查询用户得到user,为了应对并发问题,获得的的user放到对应线程中。ThreadLocal(线程相关的一个map)中,通过工具类.HostHolder

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //在请求前获取请求cookie中的ticket
        String ticket = CookieUtil.getValue(request,"ticket");
        if(ticket != null){
            //查询凭证
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            //判断凭证是否有效,当前状态有效,时间没有越过规定时间
            if(loginTicket!=null && loginTicket.getStatus()==0 && loginTicket.getExpired().after(new Date())){
                //根据凭证查询用户
                int userId = loginTicket.getUserId();
                User user = userService.findUserById(userId);
                //缓存用户方便后续使用
                //服务器处理请求是一个多线程的环境,多个请求之间存在并发问题,村用户要考虑并发问题,只存在一个变量中可能有问题。
                //想要解决多次请求并发问题要考虑使用线程进的隔离,每个请求单独存放一份,互补干扰
                //一次请求对应一个线程,请求没有处理完,线程存在,线程中存放的用户数据就还在
                hostHolder.setUser(user);
            }
        }
        return true;
    }

 2、user的使用要在模版前:user的数据从该请求线程中取,将user放到model

@Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        if(user !=null && modelAndView !=null ){
            modelAndView.addObject("loginUser",user);
        }
    }

3、模版执行后清除数据

 @Override
    //在模版执行后清除user数据
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        hostHolder.clear();
    }

 4.配置中注册拦截器

5.处理模版,,index 的header

回顾多线程的知识

拦截器需实现HandlerInterceptor接口,而WebMvcConfigurer接口是MVC配置类要实现的接口。

preHandle方法在Controller之前执行,若返回false,则终止执行后续的请求。
postHandle方法在Controller之后、模板之前执行。
afterCompletion方法在模板之后执行。
配置类需实现WebMvcConfigurer接口
通过addInterceptors方法对拦截器进行配置
可以配置忽略拦截的路径,也可以配置希望拦截的路径
ThreadLocal采用线程隔离的方式存放数据,可以避免多线程之间出现数据访问冲突。
ThreadLocal提供set方法,能够以当前线程为key存放数据。
ThreadLocal提供get方法,能够以当前线程为key获取数据。

2.7账号设置

 上传图像----服务器、云服务器,讲解服务器,有单独专题讲解如何上传云服务器。

首先设置setting的servlet 用注解的方法,实现点击设置用户,跳转到setting页面,

头像修改分为两步:修改用户头像的url,上传头像图片到服务器

修改url,通过三层架构,根据用户id来修改user表中的head_url字段;

提出请求,上传图片到服务器(不涉及数据库操作,仅在chontroller)---

上传涉及到,域名+路径+项目访问路径+

@RequestMapping(path ="/upload",method = RequestMethod.POST)
    public String updateHeader(MultipartFile headImage, Model model) {
        //先判断是否有图片对象
        if(headImage == null){
            model.addAttribute("error","您没有选择图片!");
            return "/site/setting";
        }

        String fileName = headImage.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf('.'));//得到文件后缀
        if(StringUtils.isBlank(suffix)){
            model.addAttribute("error","文件格式不正确!");
            return "/site/setting";
        }
        //随机生成文件名
        fileName = CommunityUtil.generateUUID() + suffix;
        File dest = new File(uploadPath+"/"+fileName);
        //确定文件存放路径
        try {
            //将文件上传到指定目录
            headImage.transferTo(dest);
        } catch (IOException e) {
            logger.debug("上传文件失败:"+e.getMessage());
            throw new RuntimeException("上传文件失败,服务器出现异常!");
        }
        //更新当前用户的头像路径
        //web访问路径:http://localhost:8080/community/user/header/xxx.png
        User user = hostHolder.getUser();
        String headerUrl = domain + contextPath + "/user/header/" + fileName;
        userService.updateHeaderUrl(user.getId(),headerUrl);
        return "redirect:/index";
    }

2.8检查登陆状态

 元注解: 自定义注解

@Target 自定义注解可以写在那个位置,作用在那个类型

@Retention  有效时间  编译  运行时有效

@Document  生产文档时要不要带上注解

@Inherited 用于继承,指定子类要不要继承

使用自定义注解的方式

@Target(ElementType.METHOD)  //注解用来描述方法
@Retention(RetentionPolicy.RUNTIME)//运行时有效
public @interface LoginRequired {
}

 在需要拦截的对象前加自定义注解

@LoginRequired
    @RequestMapping(path = "/setting" ,method = RequestMethod.GET)
    public String getSettingPage(Model model){
        return "/site/setting";
    }

实现登陆拦截器:拦截的对象是方法,方法被注解&&没有登陆(hostHold)不执行后续请求,重定向到登陆

Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;//请求线程中存放登录用户
    @Override
    //在controller前进行拦截,拦截那些被注解的方法
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){//判断拦截的是不是方法,springMVC的一个方法
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();//得到被拦截方法对象的方法对象--反射
            LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);//获取注解
            if(loginRequired!=null && hostHolder.getUser()==null){//该方法被登录拦截器标注 && 没有登陆
                response.sendRedirect(request.getContextPath()+"/login");//强制跳转到首页,通过response重定向
                return false;//拒绝后续的请求
            }
        }
        return true;//没有问题,没有拦截 || 已经登陆
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

WebMvcConfig中配置登陆拦截对象:

@Autowired
LoginRequiredInterceptor loginRequiredInterceptor; 
//排除一些不需要拦截的路径,静态资源不需要拦截,static目录下所有css,.png,js。。。文件
        registry.addInterceptor(loginRequiredInterceptor)
                .excludePathPatterns("/**/*.css","/**/*.png","/**/*.jpg","/**/*.jpeg","/**/*.js");//不拦截

 

//修改密码
    @LoginRequired
    @RequestMapping(path = "/updatePassword",method = RequestMethod.POST)
    public String updatePassword(String oldPassword,String newPassword,String confirmPassword,Model model){
        User user = userService.findUserById(hostHolder.getUser().getId());
        oldPassword = CommunityUtil.md5(oldPassword + user.getSalt());
        if(StringUtils.isBlank(oldPassword)){
            model.addAttribute("oldPasswordMsg","原密码没有输入!");
            logger.debug("原密码没有输入!");
            return "/site/setting";
        }
        if(!oldPassword.equals(user.getPassword())){
            model.addAttribute("oldPasswordMsg","密码输入错误!");
            logger.debug("密码输入错误!"+oldPassword+"/"+user.getPassword());
            return "/site/setting";
        }
        if(StringUtils.isBlank(newPassword)){
            model.addAttribute("newPasswordMsg","密码没有输入!");
            return "/site/setting";
        }
        if(StringUtils.isBlank(confirmPassword)){
            model.addAttribute("confirmPasswordMsg","密码不能呢个!");
            return "/site/setting";
        }
        if(!confirmPassword.equals(newPassword)){
            model.addAttribute("confirmPasswordMsg","密码不匹配!");
            return "/site/setting";
        }
        if(userService.updatePassword(user.getId(),newPassword)>0){
            logger.debug("密码更新成功!");
            return "redirect:/login";
        }else{
            logger.debug("密码更新失败!");
            model.addAttribute("confirmPasswordMsg","密码更新失败!");
            return "/site/setting";
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值