日志与定时任务

一、权限管理

拦截器方式

自定义拦截器,判断是否得到凭证,没有,则拦截

//自定义拦截器类
public class MyHandler implements HandlerInterceptor {
    @Override  //拦截器的拦截处理的handler
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object o = request.getSession().getAttribute("username");
        if(o==null){ //没有登录凭证,则到达登录页面
            response.sendRedirect("/login.html");
            return false;
        }
        return true;
    }
}

在mvc容器中配置拦截路径

<mvc:interceptors>  <!-- 配置拦截路径 -->
    <mvc:interceptor>
        <mvc:mapping path="/customer/**"/>
        <mvc:mapping path="/app/**"/>
        <bean class="com.qf.handler.MyHandler"></bean>
    </mvc:interceptor>
</mvc:interceptors>

登录用户模块的表设计

create table user(
	id int primary key auto_increment,
  username varchar(20),
  password varchar(20)
);
insert into user(username,password) values('zs','123');
insert into user(username,password) values('ls','444');

再创建controller,service,dao

前端页面创建login.html并准备标签

<div class="layui-row" style="margin-top:10%">
    <div class="layui-col-xs6 layui-col-md-offset2">
        <form class="layui-form" action="#" method="post">
            <div class="layui-form-item">
                <label class="layui-form-label">用户名</label>
                <div class="layui-input-block">
                    <input type="text" name="username" value="zs" lay-verify="text" autocomplete="off" placeholder="请输入账号"
                           class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">密码</label>
                <div class="layui-input-block">
                    <input type="text" name="password" value="123" lay-verify="required" placeholder="请输入密码" autocomplete="off"
                           class="layui-input">
                </div>
            </div>

            <div class="layui-form-item">
                <div class="layui-input-block">
                    <button class="layui-btn" lay-submit lay-filter="login">登录</button>
                    <span id="msg"></span>
                </div>
            </div>
        </form>
    </div>
</div>

再使用提交的触发事件,通过ajax与后端交互

layui.use(['form'], function(){
    var form = layui.form;
    //监听提交
    form.on('submit(login)', function(data){
        layer.msg(JSON.stringify(data.field));

        $.ajax({
            type:"post",
            url:"/user/login",
            data:data.field,
            success:function (res) {
                if(res==1){  //登录成功,则跳到首页
                    location.href="/index.html";
                }else if(res==0){
                    $("#msg").html("<font color='red'>登录失败</font>");
                }
            }
        })
        return false;
    });
});

在首页判断是否有凭证

function openRight(url) {
    //在加载数据前,先判断是否有登录凭证;如果没有,跳到登录;有则加载
    $.ajax({
        type:"get",
        url:"/user/valid",
        success:function(res){
            if(res==0){ //凭证校验失败
                location.href="/login.html";  //调整
            }else if(res==1){
                $("#main").load(url); //main就是用于显示数据的div的id
            }
        }
    })
}

拦截细节

前端都是使用html页面,所有如果需要凭证的判断,都需要通过ajax发请求。

拦截的是客户模块,应用模块;用户模块本身是放行的,没有进入拦截路径。

拦截测试:

  1. 直接访问/customer/show; 拦截住则进入登录
  2. 点击客户管理和应用管理的测试,拦截住,则进入登录(需要在首页中进行判断凭证)

二、日志配置

日志概述:在项目开发和上线的过程中,都离不开日志;开发时日志信息打印到控制台,可以分析程序的执行情况;上线后通过分析日志,可查找异常的情况。

日志级别:从低到高分别是:trace,debug,info,warn,error,fatal

配置

导包

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

日志测试

public class Log4jTest {
    public static void main(String[] args) {
        //默认指定了info级别打印,所以info以后的信息都出现
        Log log = LogFactory.getLog(Log4jTest.class);
        log.trace("trace log");  //跟踪日志
        log.debug("debug log");  //调试日志
        log.info("info log");    //信息日志
        log.warn("warn log");    //警告日志
        log.error("error log");  //错误日志
        log.fatal("fatal log");  //致命日志
    }
}

应用日志

在项目中使用日志;需要自己定制打印出不同的级别;后续根据打印的日志级别进行分析问题

@GetMapping("/show")
public TableData<Customer> show(int page,int limit,Customer customer){
    //日志的应用:
    Log log = LogFactory.getLog(CustomerController.class);
    if(page==1){
        log.info("page one info!!");
    }else if(page==2){
        log.info("page two info!!!");
    }
    try {
        int i=1/0;
    }catch (Exception e){
        log.error("arth exception!!");
    }
    PageInfo<Customer> pi = customerService.selectCustomers(page,limit,customer);
    TableData<Customer> td = new TableData<>(0,"no data",pi.getTotal(),pi.getList());
    return td;
}

说明:日志的级别配置,都是自己定制的;如果需要将日志配置到文件中,则需要在resources中放置log4j.xml配置文件。

三、定时任务

概述:定制在某个时间点,触发某种执行任务

应用场景:每月还花呗,每天的闹钟触发(类似之前前端讲的定时器)

三个核心组件:任务(Job)、触发器(定制规则)、调度器(启动任务)

测试案例

导入依赖包

<dependencies>
    <!--Quartz任务调度-->
    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.3</version>
    </dependency>
</dependencies>

定时任务的执行

//定制定时任务实现Job接口
public class MyQuartz implements Job {
    @Override  //重写方法中就是需要执行的任务
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail(); //获取任务详情
        String name = detail.getKey().getName();
        String group = detail.getKey().getGroup();
        System.out.println("任务名:"+name+";任务组:"+group);
        System.out.println(new Date()); //后续在项目中,可能是触发执行某个功能
    }
}

三个核心组件去配置规则:调度器,任务,触发器

//创建触发器,定制触发规则
Trigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("tri1","gro1")
    .startNow()  //立即启动触发器规则
    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                  .withIntervalInSeconds(3)  //每隔3秒触发
                  .repeatForever())  //一直持续触发 //触发规则
    //结束时间:指定年月日,时分秒
    .endAt(new GregorianCalendar(2023,10,20,12,0,0).getTime())   //结束时间
    .build();
//创建任务详情,将要执行的任务的反射对象引入
JobDetail detail = JobBuilder.newJob(MyQuartz.class)
    .withIdentity("job1","job_gro1")
    .build();
//创建调度器,并启动
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(detail,trigger);  //调度任务,传入触发器和任务对象
scheduler.start();   //启动

CronTrigger

前面的Trigger只是处理一些简单的触发规则;如果需要更灵活及强大的触发器,则需要使用CronTrigger。

//CronTrigger触发器
CronTrigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("t1","g1")
    .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
    .build();

cron表达式

cron表达式,通过:秒 分 时 日 月 星期 [年] ,来定制不同的规则。

例如:下面的荣表达式,表示每日12点触发; 解析:从左往右解读,*代表“每”

0 0 12 * * ? : 秒 分 时 日 月 星期

| 表示式                   | 说明                                                         |
| ------------------------ | ------------------------------------------------------------ |
| 0 0 12 * * ?             | 每天12点运行                                                 |
| 0 15 10 * * ?            | 每天10:15运行                                                |
| 0 15 10 * * ? 2008       | 在2008年的每天10:15运行                                     |
| 0 * 14 * * ?             | 每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。 |
| 0 0/5 14 * * ?           | 每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。    |
| 0 0/5 14,18 * * ?        | 每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。 |
| 0 0-5 14 * * ?           | 每天14:00点到14:05,每分钟运行一次。                         |
| 0 0-5/2 14 * * ?         | 每天14:00点到14:05,每2分钟运行一次。                        |
| 0 10,44 14 ? 3 4         | 3月每周三的14:10分和14:44分,各运行一次。                    |
| 0 15 10 ? * 2-6          | 每周一,二,三,四,五的10:15分运行一次。                    |
| 0 15 10 15 * ?           | 每月15日10:15分运行。                                        |
| 0 15 10 L * ?            | 每月最后一天10:15分运行。                                    |
| 0 15 10 ? * 6L           | 每月最后一个星期五10:15分运行。【此时天必须是"?"】           |
| 0 15 10 ? * 6L 2007-2009 | 在2007,2008,2009年每个月的最后一个星期五的10:15分运行。      |

spring整合任务

和测试案例类似,将三个核心组件以bean方式注入数据。

导包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

spring的容器配置

<!-- 产生任务bean -->
<bean id="myjob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="name" value="j1"></property>
    <property name="group" value="g1"></property>
    <property name="jobClass" value="com.qf.handler.MyQuartz"></property>
</bean>
<!-- 产生cron触发器 -->
<bean id="mytri" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="name" value="t1"></property>
    <property name="group" value="tg1"></property>
    <!-- 触发器中绑定任务 -->
    <property name="jobDetail" ref="myjob"></property>
    <!-- 触发器规则-->
    <property name="cronExpression" value="0/3 * * * * ?"></property>
</bean>
<!-- 产生调度器 -->
<bean id="schedur" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers"> <!-- 容器中引入触发器bean -->
        <list>
            <ref bean="mytri"/>
        </list>
    </property>
</bean>

四、shiro

概述

shiro是一套安全框架;应用包括:身份认证(类似拦截器的用法),用户授权

shiro和spring security类似,都是做认证和授权的安全框架。

区别:shiro可以脱离框架来应用;但spring security必须有spring框架才能使用。

核心功能

核心功能有4个,分别是认证,授权,会话管理和加密:

  1. 认证(Authentication):登录,证明用户是谁的行为。
  2. 授权(Authorization):访问控制的过程,即确定“谁可以访问”什么。
  3. 会话管理(Session Management):即使在非Web或EJB应用程序中也可以管理用户特定的会话。
  4. 密码学(Cryptography):使用加密算法保持数据安全性,同时易于使用。

身份认证

导入依赖包

<!-- shiro核心支持 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- shiro Web支持 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- shiro Spring支持 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- shiro 缓存支持 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>

web.xml中的shiro过滤器配置

<!-- Shiro Filter -->
<filter>
    <!--
        DelegatingFilterProxy 过滤器代理对象,默认情况下spring会ioc中找和filter-name,相同的bean
        可以使用initparam 配置tagerbeanname找对应的bean(spring需要的配置对象),
        建议不要改
         -->
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

bean-shiro容器中的配置

<!-- 1.配置SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- cacheManager缓存管理器 -->
    <property name="cacheManager" ref="cacheManager"/>
    <!-- session管理方式 -->
    <property name="sessionMode" value="native"/>
    <!--        <property name="realm" ref="jdbcRealm"/>-->
</bean>
<!-- cacheManager缓存管理器 需要ehcache的jar包以及配置文件 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <!-- 直接指定默认的ehcache -->
    <!--<property name="cacheManager" ref="ehCacheManager"/> -->
    <!-- 通过ehcache配置缓存 -->
    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 3.配置realm -->
<!-- 3.1 配置 实现了Realm接口的类 -->
<bean id="jdbcRealm" class="com.cos.shiro.realm.MyRealm">
    <!-- @TODO 设置Realm 创建类继承org.apache.shiro.realm.AuthenticatingRealm,可以先写空的来测试框架组合是否成功 -->
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after
        the lifecycleBeanProcessor has run: -->
<!-- 使用shiro注解,前提为需要先配置 LifecycleBeanPostProcessor,并且如果注解设置在controller需要配置到spring-mvc里面 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>
<!-- 配置 shiro过滤器,真正处理客户端请求的类,id值必须和web.xml配置的 shiroFilter 的filtername一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <!-- 安全管理器 -->
    <property name="securityManager" ref="securityManager"/>
    <!-- 登陆页面,如果认证没有通过会重定向到这个页面 -->
    <property name="loginUrl" value="/login.jsp"/>
    <!-- 登陆成功页面 -->
    <property name="successUrl" value="/index.jsp"/>
    <!-- 授权失败跳转的页面 -->
    <property name="unauthorizedUrl" value="/login.jsp"/>
    <!-- 过滤器链 ,用于配置那些页面需要验证保护,以及访问这些页面需要的权限-->
    <property name="filterChainDefinitions">
        <!-- 注意:过滤器的工作顺序:shiro采取第1次匹配优先,第一次匹配后后面的过滤器链不会匹配,
                    顺序不当可能出现”302 not found”错误 常用过滤器通俗解释:(过滤路径支持ant风格)
                logout :退出登陆过滤器:默认跳转项目根路径访问地址(可自定义)
                anon:匿名过滤器:不需要认证就可以进行访问:例如公司的首页
                authc:认证过滤器:客户端只有认证通过之后才能访问(例如个人信息)
                roles:权限过滤器:客户端访问制定路径,只有满足指定的角色才能访问 -->
        <value>
            /login.jsp = anon
            /user/login = anon
            /user/logout=logout
            /** = authc
        </value>
    </property>
</bean>

再将缓冲配置文件ehcache.xml导入即可。

过滤规则:

<value>
    /login.jsp = anon   <!--登录是放行的,任何用户都能访问-->
    /user/login = anon  <!--后台路径页放行-->
    /user/logout=logout  <!--处理缓存-->
    /** = authc          <!--其它的路径访问都需要认证-->
</value>

认证流程

  • Controller
  1. 获取当前的Subject即访问用户抽象对象,调用SecurityUtils.getSubject()
  2. 根据传入的username及password通过token进行登录认证
  3. 具体的登录认证,通过realm完成
  • realm
  1. 获取subject中token对象,取出用户名
  2. 调用数据库方法返回对象,查询username是否存在
  3. 如用户不存在则抛出异常

控制层传入用户名和密码,并交给token存数据

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login(String username,String password){
        System.out.println("username-->"+username+";password-->"+password);
        //获取subject对象--用户对象
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated()){//如果没有认证成功
            //传入用户名和密码生成token对象(共享对象),在realm中可获取
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
            subject.login(token);  //登录认证
        }
        return "redirect:/index.html";
    }
}

subject调用登录方法,会进入realm类中进行身份认证;因为在realm中可连接数据库

public class MyRealm extends AuthenticatingRealm {
    @Resource
    private UserDao userDao;
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //token强转,即可调用属性username  和控制层的token是同一个对象
        UsernamePasswordToken utoken = (UsernamePasswordToken) token;
        //根据用户名获取user对象   用户名的校验
        User user = userDao.selectByName(utoken.getUsername());
        if(user==null){
            throw  new UnknownAccountException("用户名不存在");
        }
        //密码校验
        SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
        return auth;
    }
}

五、总结与作业

总结

1.权限管理
使用拦截器方式处理;前端都是html,通过ajax方式交互
拦截细节
2.日志配置
导包-测试log4j;应用到项目中(自己定制)
3.定时任务
三个核心组件(任务,触发器,调度器)(重点)
测试案例;conTrigger的使用
cron表达式(重点);spring整合任务
4.shiro
概述; 核心功能:认证,授权(重点),还有会话与加密
身份认证的应用与执行流程

作业

登录成功,记录凭证,并在首页显示用户名; 点击“退了”,则清除用户名,显示“请登录”;点击“请登录”,则可跳到登录页面