crm系统登录模块——增加非法请求拦截功能,改进异常处理方式,增加记住密码功能

        书接上文(欢迎读者阅读我之前发布的crm系列博客)我们接着对crm系统登录模块做一些功能改进。

一、增加非法请求拦截功能

(一)思路分析

1、拦截目的:放行资源给合法访问用户,过滤非法访问用户(在请求目标资源之前执行该拦截)

2、 拦截方法:判断访问合法与否?返回布尔类型值

3、 返回值为布尔类型:

         * true:表示合法访问,放行资源;

        * false:非法访问,资源不放行,且抛出用户未登录异常

                (由全局异常控制类做判断后跳转到登录页面,下面标题二有全局异常控制类介绍)  

4、如何判断用户合法与否?

        (这里的判断指标是用户是否处于登陆状态)

        * 4.1、拿到用户访问时的cookie信息

        * 4.2、从cookie获取userId

        * 4.3、根据userId去数据库查询是否存在该用户

(二)具体实现代码

        1、定义非法访问拦截器
package com.msb.crm.interceptor;

import com.msb.crm.dao.UserMapper;
import com.msb.crm.exceptions.NoLoginException;
import com.msb.crm.utils.LoginUserUtil;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 定义非法访问拦截器
 * 该方法返回值:布尔类型
 * 继承HandlerInterceptorAdapter适配器
 */
public class NoLoginInterceptor extends HandlerInterceptorAdapter {

    @Resource
    private UserMapper userMapper;
    /**
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //从cookie获取userId
        Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
        //判断userId是否为空且数据库是否有该用户记录
        if (null == userId && userMapper.selectByPrimaryKey(userId)== null){
            //抛出未登陆异常(该异常交给全局异常处理器捕获并作处理)
            throw new NoLoginException();
        }

        return true;
    }
}

        2、定义使拦截器生效的配置类

package com.msb.crm.config;

import com.msb.crm.interceptor.NoLoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * 使拦截器生效的配置类
 */
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
    //1、将拦截器实例化(即获得一个放拦截器的变量),采取注解方式实例化(将方法返回的对象交给IOC管理)
    @Bean
    public NoLoginInterceptor noLoginInterceptor(){
        return new NoLoginInterceptor();
    }
    //2、给拦截器的变量里面放一个拦截器

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(noLoginInterceptor())
        //设置需要拦截的资源
        .addPathPatterns("/**")   //默认拦截所有资源
        //设置不需要拦截的资源(public包下的资源以及首页、登录页资源
        .excludePathPatterns("/css/**","/images/**","/js/**","/lib/**","/index","/user/login");
    }


}

以上代码存放目录

 

二、改进异常处理方式

1、将之前Controller层的try(){}catch{}异常处理方式替换

        用全局异常控制器捕捉并处理

2、新建类GlobalExceptionResolver类

      全局异常控制器由该类充当  

      类文件存放位置如下(和启动类同级,都在crm包下)

 3、具体实现代码:
package com.msb.crm;

import com.alibaba.fastjson.JSON;
import com.msb.crm.base.ResultInfo;
import com.msb.crm.exceptions.NoLoginException;
import com.msb.crm.exceptions.ParamsException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 根据方法返回值类型对异常分类并捕获处理(取代控制器里的异常捕获)
 *       视图
 *       json
 *  如何判断方法返回的是视图  还是 json?
 *     约定:如果方法级别配置@ResponseBody  方法响应内容为json  反之 方法响应内容为html页面
 */
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

        /**
         * 捕获并处理未登陆异常
         *     先判断是否抛出未登陆异常,若是则重定向到登录页让用户进行登录。
         */
        if(ex instanceof NoLoginException){
            //重定向到登录页
            ModelAndView mv = new ModelAndView("redirect:/index");
            return mv;
        }

        /**
         * 设置默认异常处理(返回视图)
         */
        ModelAndView modelAndView = new ModelAndView();
        //设置异常信息
        modelAndView.setViewName("error");
        modelAndView.addObject("code",500);
        modelAndView.addObject("msg","系统异常,请稍后再试");

        //判断HandlerMethod( instanceof 运算符用于测试对象是否是指定类型(类或子类或接口)的实例)
        if (handler instanceof HandlerMethod){
            //类型强转
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //通过反射获得方法上声明的ResponseBody注解对象
            ResponseBody responseBody = handlerMethod.getMethod().getDeclaredAnnotation(ResponseBody.class);
            //判断responseBody是否为空(若==空:则表示该方法返回的是视图;否则返回的是Json)
            if (responseBody == null){
                /**
                 * 方法返回视图
                 */
                //判断异常类型
                if(ex instanceof ParamsException){
                    ParamsException p = (ParamsException) ex;
                    //设置异常信息
                    modelAndView.addObject("p",p.getCode());
                    modelAndView.addObject("msg",p.getMsg());
                }
                return modelAndView;

            }else{
                /**
                 * 方法返回数据
                 */
                //设置默认的异常处理
                ResultInfo resultInfo = new ResultInfo();
                resultInfo.setCode(500);
                resultInfo.setMsg("发生异常,请重试!!");
                //判断异常类型是否为自定义异常
                if(ex instanceof ParamsException){
                    ParamsException p = (ParamsException) ex;
                    resultInfo.setCode(p.getCode());
                    resultInfo.setMsg(p.getMsg());
                }
                //设置响应类型及编码格式(响应Json格式的数据)
                response.setContentType("application/Json;charset=UTF-8");
                //得到字符输出流
                PrintWriter out = null;
                try {
                    out = response.getWriter();
                    //将需要返回的异常对象转换为Json格式字符
                    String json = JSON.toJSONString(resultInfo);
                    //输出数据
                    out.write(json);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    //如果out不为空,则关闭??????????????????????????????????
                    if (out != null){
                        out.close();
                    }
                }
                return null;
            }

        }
        return modelAndView;
    }
}

Controller层的try(){}catch{} 就可以省略掉了,如下我把他们打了注释

package com.msb.crm.controller;

import com.msb.crm.base.BaseController;
import com.msb.crm.base.ResultInfo;
import com.msb.crm.model.UserModel;
import com.msb.crm.service.UserService;
import com.msb.crm.utils.LoginUserUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 接收前台请求的控制器:对接前台index页面里发送的Ajax登录请求
 */
//@Controller标记该类是一个控制器(控制器负责接收请求、处理请求,并返回响应)
@Controller
@RequestMapping("user")
public class UserController extends BaseController {
    //本层会调用service层对象里的方法,在该类里注入service层对象
    @Resource
    private UserService userService;
    @Resource
    private ResultInfo resultInfo;
    //该注解作用:给前端机动部(index.js)提供访问路径并限定请求方式
    @PostMapping("login")
    //以jason格式返回对象类型数据要加这个注解@ResponseBody(如果返回页面不需要加该注解)
    @ResponseBody
    //index.js根据访问路径携带表单内容给UserController发来请求;
    // UserController形参接收表单数据,用ResultInfo类型变量返回封装好的userModel信息给index.js
    public ResultInfo userLogin(String userName, String userPwd) {
        

        UserModel userModel = userService.userLogin(userName, userPwd);
        resultInfo.setResult(userModel);
        /*try {

              //controller层对service层的数据进行接收(不建议直接用User类对象接收,因为其中有很多我们不需要的属性值)
              //所以我们新建一个model层,里面定义UserModel类,用于把我们需要返回的数据信息封装到该类里

            UserModel userModel = userService.userLogin(userName, userPwd);
            //将service层返回的用户信息用userModel接收
            //设置登陆成功后resultInfo的response值为userModel
            resultInfo.setResult(userModel);
        } catch (ParamsException p) {
            //没有异常时执行try里的代码块,否则执行catch里的代码段
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
            p.printStackTrace();
        } catch (Exception e) {
            resultInfo.setCode(500);
            resultInfo.setMsg("登陆失败");
        }*/
        return resultInfo;
    }

    @PostMapping("updatePassword")
    @ResponseBody
    public ResultInfo updateUserPassword(HttpServletRequest request,String oldPassword,String newPassword,String repeatPassword){
       

        //从request中获取cookie中的userIdStr然后调用工具得到userId
        Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
        //调用service层修改密码方法
        userService.updatePassword(userId,oldPassword,newPassword,repeatPassword);
       /*try{
           //从request中获取cookie中的userIdStr然后调用工具得到userId
           Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
           //调用service层修改密码方法
           userService.updatePassword(userId,oldPassword,newPassword,repeatPassword);
       }catch(ParamsException p){
           resultInfo.setCode(p.getCode());
           resultInfo.setMsg(p.getMsg());
           p.printStackTrace();
       }catch (Exception e){
           resultInfo.setCode(500);
           resultInfo.setMsg("修改密码失败!");
           e.printStackTrace();
       }*/
        return resultInfo;

    }
    /**
     * 接收修改密码界面的请求并执行资源返回操作
     * @return
     */
    @RequestMapping("toPasswordPage")
    public String toPasswordPage(){
        return "user/password";
    }
}

三、增加记住密码功能

1、前端静态页(index.ftl)实现 

        (1)在前端的index.ftl文件中添加这部分元素

         (2)具体代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>后台管理-登陆</title>
    <#include "common.ftl">
    <link rel="stylesheet" href="${ctx}/css/index.css" media="all">
</head>
<body>
<div class="layui-container">
    <div class="admin-login-background">
        <div class="layui-form login-form">
            <form class="layui-form" action="">
                <div class="layui-form-item logo-title">
                    <h1>CRM后端登录</h1>
                </div>
                <div class="layui-form-item">
                    <label class="layui-icon layui-icon-username" for="username"></label>
                    <input type="text" name="username" lay-verify="required|account" placeholder="用户名或者邮箱" autocomplete="off" class="layui-input" >
                </div>
                <div class="layui-form-item">
                    <label class="layui-icon layui-icon-password" for="password"></label>
                    <input type="password" name="password" lay-verify="required|password" placeholder="密码" autocomplete="off" class="layui-input" >
                </div>
                <div class="layui-form-item">
                    <input type="checkbox" name="rememberMe" id="rememberMe" value="true" lay-skin="primary" title="记住密码">
                </div>
                <div class="layui-form-item">
                    <button class="layui-btn layui-btn-fluid" lay-submit="" lay-filter="login">登 入</button>
                </div>
            </form>
        </div>
    </div>
</div>
<script src = "${ctx}/js/index.js" charset="utf-8"></script>
</body>
</html>
2、机动组(index.js)实现

(1)在前端的index.js文件中更改/添加这部分内容 

         (2)具体代码 

layui.use(['form','jquery','jquery_cookie'], function () {
    var form = layui.form,
        layer = layui.layer,
        $ = layui.jquery,
        $ = layui.jquery_cookie($);


    // layui 用户登录 表单提交
    /**
     * 表单submit提交
     * form.on('submit(login)',function (data) {
     *     return false;  // 阻止表单提交/跳转操作
     * }
     */
    form.on('submit(login)',function (data) {
        // 键值对形式获取表单元素全部字段内容
        data = data.field;
        console.log(data);

        /**
         *  用户名 密码 非空校验
         */
        if(data.username=="undefined" || data.username=="" || data.username.trim()==""){
            layer.msg("用户名不能为空!");
            return false;
        }

        if(data.password=="undefined" || data.password=="" || data.password.trim()==""){
            layer.msg("用户密码不能为空!");
            return false;
        }

        /**
         * 对接后台收发请求的具体执行者(被展示页面index.ftl调用),发送Ajax请求,传递用户名与密码,请求的资源路径:用户登录页面
         * 指定用post请求方式,请求数据json格式:键名与后台需求变量名一致,值名与前端展示页index.ftl里对应type属性的name属性值一致
         */

        $.ajax({
            type:"post",
            url:ctx+"/user/login",
            data:{
                //格式~~~~后台形参名:data.前台表单name属性值
                userName:data.username,
                userPwd:data.password
            },
            dataType:"json",
            success:function (data) {
                //data回调函数:是接收到UserController里的返回值resultinfo后被执行的函数
                console.log(data)
                if(data.code==200){
                    layer.msg("用户登录成功",function () {
                        var result =data.result;
                        //记住我功能实现方法一:
                        //判断用户是否选择记住密码
                        // if($("#rememberMe").prop("checked")){
                        //     //用户选则记住,设置cookie对象xx天内生效,用户也设置到cookie中
                        //     $.cookie("userIdStr",result.userIdStr,{expires:7});
                        //     $.cookie("userName",result.userName,{expires:7});
                        //     $.cookie("trueName",result.trueName,{expires:7});
                        // }else {
                        //     //用户没选择记住密码则cookie仅仅设置用户信息
                        //     $.cookie("userIdStr",result.userIdStr);
                        //     $.cookie("userName",result.userName);
                        //     $.cookie("trueName",result.trueName);
                        // }
                        //记住我功能实现方法二:
                        $.cookie("userIdStr",result.userIdStr);
                        $.cookie("userName",result.userName);
                        $.cookie("trueName",result.trueName);
                        //给type=checkbox的复选框绑定事件:
                        // 如果用户勾选,则给该用户的cookie里设置expires项:后面的数字为免登陆天数
                        if($("input[type='checkbox']").is(":checked")){
                            $.cookie("userIdStr",result.userIdStr,{expires:7});
                            $.cookie("userName",result.userName,{expires:7});
                            $.cookie("trueName",result.trueName,{expires:7});
                        }
                        window.location.href=ctx+"/main"; //这里的main是访问后台控制器路径

                    })
                }else{
                    layer.msg(result_data.msg);
                }
            }
        });
        //阻止表单提交/跳转操作
        return false;
    })
});

到此我们crm系统登录模块的基本功能就基本完成了。感谢大家学习观看并提出见解。

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值