71-cms项目(1)-Layui封装和登录功能实现

618 阅读12分钟

cms项目(1)--Layui封装和登录功能实现

笔记中涉及资源:

链接:pan.baidu.com/s/18cgWBpiV…

提取码:Coke


一、cms系统多模块的构建

①:cms系统各模块的作用

模块名模块作用
cms-portal前台页面 控制器
cms-serviceservice层 dto(接收前台数据)
cms-daodao层entity(数据库) enums mapper
cmS-context上下文层 承上启下
cms-core核心基础功能

②:创建多模块

image.png

  • 修改编码格式

image.png

③:如何让idea识别web项目

image.png

idea识别web项目有两个重要的条件:

    1. IDEA版本必须是ULTIMATE版本
    1. plugins里启用 Java EE:EJB,JPA,Servlets插件.

image.png

  • 将对应模块设置为web模块

image.png

image.png

image.png

二、ssm框架整合

①:相关配置文件整合

image.png

0、导入相关依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <springVersion>4.3.20.RELEASE</springVersion>
    <mybatisVersion>3.4.1</mybatisVersion>
    <!--mybatis和spring集成-->
    <mybatisSpringVersion>1.3.0</mybatisSpringVersion>
    <mybatisTypehandlersJsr310Version>1.0.1</mybatisTypehandlersJsr310Version>
    <servletVersion>9.0.4</servletVersion>
    <aspectjweaverVersion>1.7.3</aspectjweaverVersion>
    <logbackVersion>1.1.2</logbackVersion>
    <mysqlVersion>8.0.11</mysqlVersion>
    <guavaVersion>20.0</guavaVersion>
    <commonsLang3Version>3.5</commonsLang3Version>
    <commonsCollectionsVersion>3.2.1</commonsCollectionsVersion>
    <jodaTimeVersion>2.3</jodaTimeVersion>
    <hashidsVersion>1.0.1</hashidsVersion>
    <commonsNetVersion>3.1</commonsNetVersion>
    <commonsFileupload>1.3.3</commonsFileupload>
    <commonsIoVersion>2.0.1</commonsIoVersion>
    <pagehelperVersion>4.2.1</pagehelperVersion>
    <mybatisPaginatorVersion>1.2.17</mybatisPaginatorVersion>
    <jsqlparserVersion>0.9.4</jsqlparserVersion>
    <commonsCodec>1.10</commonsCodec>
    <commonsLangVersion>2.6</commonsLangVersion>
    <commonsLoggingVersion>1.1.1</commonsLoggingVersion>
    <zxingVersion>2.1</zxingVersion>
    <gsonVersion>2.3.1</gsonVersion>
    <hamcrestCoreVersion>1.3</hamcrestCoreVersion>
    <jedisVersion>2.9.0</jedisVersion>
    <flywayVersion>4.1.0</flywayVersion>
    <lombokVersion>1.16.20</lombokVersion>
    <validatorVersion>5.4.0.Final</validatorVersion>
    <mapstructVersion>1.2.0.Final</mapstructVersion>
    <kaptchaVersion>2.3.2</kaptchaVersion>
    <druidVersion>1.0.7</druidVersion>
    <log4jVersion>1.2.17</log4jVersion>
    <shiroVersion>1.3.2</shiroVersion>
    <freemarkerVersion>2.3.23</freemarkerVersion>
    <fastjsonVersion>1.2.55</fastjsonVersion>
    <ehcacheCoreVersion>2.6.6</ehcacheCoreVersion>
    <mybatisEhcacheVersion>1.1.0</mybatisEhcacheVersion>
    <luceneVersion>4.6.1</luceneVersion>
    <fasterxmlUuidVersion>3.1.4</fasterxmlUuidVersion>
</properties>

<dependencies>
    <!--servlet-->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-servlet-api</artifactId>
        <version>${servletVersion}</version>
    </dependency>
    <!--springMvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${springVersion}</version>
    </dependency>
    <!--Java对象(POJO)和 XML 文档之间来回转换。-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
        <version>${springVersion}</version>
    </dependency>
    <!--spring操作jdbc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${springVersion}</version>
    </dependency>
    <!--spring AspectJ-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${springVersion}</version>
    </dependency>
    <!--包含支持缓存Cache(ehcache)、JCA、JMX、 邮件服务(Java Mail、COS Mail)、任务计划Scheduling(Timer、Quartz)方面的类-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${springVersion}</version>
    </dependency>
    <!--spring 事务管理-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${springVersion}</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectjweaverVersion}</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatisSpringVersion}</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatisVersion}</version>
    </dependency>
    <!--Mybatis支持JSR310标准-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-typehandlers-jsr310</artifactId>
        <version>${mybatisTypehandlersJsr310Version}</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysqlVersion}</version>
    </dependency>
    <!--druid数据库连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druidVersion}</version>
    </dependency>
    <!--日志-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4jVersion}</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
    </dependency>
</dependencies>

1、datasource.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///cms?useAffectedRows=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

# 初始化大小,最小,最大
datasource.initialSize=5
datasource.minIdle=5
datasource.maxActive=20
# 配置获取连接等待超时的时间
datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
datasource.minEvictableIdleTimeMillis=300000
# 校验SQL,Oracle配置 datasource.validationQuery=SELECT 1 FROM DUAL,如果不配validationQuery项,则下面三项配置无用
datasource.validationQuery=SELECT 'x'
datasource.testWhileIdle=true
datasource.testOnBorrow=false
datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
datasource.poolPreparedStatements=true
datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
datasource.filters=stat,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
datasource.useGlobalDataSourceStat=true

2、applicationContext-datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--配置sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--mapper.xml位置-->
        <property name="mapperLocations">
            <array>
                <value>classpath*:com/it/dao/mapper/mappers/**.xml</value>
            </array>
        </property>
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="mapUnderscoreToCamelCase" value="true"/>
                <!-- 数据库超过25000秒仍未响应则超时 -->
                <property name="defaultStatementTimeout" value="25000"/>
                <!--开启缓存支持-->
                <property name="cacheEnabled" value="true"/>
                <!--在mybatis中,如果要实现懒加载,必须是如下的配置,两个开关都要设置-->
                <property name="lazyLoadingEnabled" value="true"/>
                <property name="aggressiveLazyLoading" value="false"/>
                <!--可以阻挡不相干的操作触发,实现懒加载-->
                <property name="lazyLoadTriggerMethods" value=""/>
            </bean>
        </property>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${datasource.initialSize}" />
        <property name="minIdle" value="${datasource.minIdle}" />
        <property name="maxActive" value="${datasource.maxActive}" />
        <!--配置获取连接等待超时的时间-->
        <property name="maxWait" value="${datasource.maxWait}" />
        <!--配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒-->
        <property name="timeBetweenEvictionRunsMillis" value="${datasource.timeBetweenEvictionRunsMillis}" />
        <!--配置一个连接在池中最小生存的时间,单位是毫秒-->
        <property name="minEvictableIdleTimeMillis" value="${datasource.minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="${datasource.validationQuery}" />
        <property name="testWhileIdle" value="${datasource.testWhileIdle}" />
        <property name="testOnBorrow" value="${datasource.testOnBorrow}" />
        <property name="testOnReturn" value="${datasource.testOnReturn}" />
        <!--打开PSCache,并且指定每个连接上PSCache的大小-->
        <property name="poolPreparedStatements" value="${datasource.poolPreparedStatements}" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${datasource.maxPoolPreparedStatementPerConnectionSize}" />
        <!--开启Druid的监控统计功能,StatFilter可以和其他的Filter配置使用-->
        <property name="filters" value="${datasource.filters}" />
        <!--通过connectProperties属性来打开mergeSql功能;慢SQL记录-->
        <property name="connectionProperties" value="${datasource.connectionProperties}" />
        <!--合并多个DruidDataSource的监控数据-->
        <property name="useGlobalDataSourceStat" value="${datasource.useGlobalDataSourceStat}" />
        <!--监控统计拦截的filters-->
        <property name="proxyFilters">
            <list>
                <ref bean="stat-filter"/>
            </list>
        </property>
    </bean>

    <!--mapper扫描接口-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.it.dao.mapper"/>
    </bean>

    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
        <property name="rollbackOnCommitFailure" value="true"/>
    </bean>

    <!--  使用@Transactional进行声明式事务管理需要声明下面这行  -->
    <tx:annotation-driven proxy-target-class="true"/>

    <!--慢sql记录-->
    <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
        <property name="mergeSql" value="true"/>
        <property name="slowSqlMillis" value="10000" />
        <property name="logSlowSql" value="true" />
    </bean>
</beans>

3、applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--包扫描 use-default-filters 属性的默认值为 true 即使用默认的 Filter 进行包扫描,
    而默认的 Filter 对标有 @Service,@Controller和@Repository 的注解的类进行扫描-->
    <context:component-scan base-package="com.it">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    <!--读取配置文件-->
    <bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="order" value="2"/>
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="locations">
            <list>
                <value>classpath:datasource.properties</value>
            </list>
        </property>
        <property name="fileEncoding" value="utf-8"/>
    </bean>

    <!--引入其他-->
    <import resource="classpath:applicationContext-datasource.xml"/>
</beans>

4、servlet-admin.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.it.portal.controller.admin" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- 配置映射器与适配器 -->
    <mvc:annotation-driven/>
</beans>

5、log4j.properties

log4j.rootLogger=INFO,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.apache=INFO
log4j.appender.stdout.Encoding=UTF-8

6、web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>cms-pro</display-name>

  <!--容器配置文件的位置-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <!--web容器和spring容器整合-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>adminServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:servlet-admin.xml</param-value>
    </init-param>
    <!--servlet配置 容器启动时启动这个servlet-->
    <load-on-startup>2</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>adminServlet</servlet-name>
    <url-pattern>/admin/cms/*</url-pattern>
  </servlet-mapping>

</web-app>

②:数据准备(创建数据库和表)

image.png

③:代码测试

根据需要自行导入包(各模块)之间的依赖

1、创建mapper文件

image.png

  • TestMapper
public interface TestMapper {

    int count();
}
  • TestMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.dao.mapper.TestMapper">

    <select id="count" resultType="java.lang.Integer">
        select count(*) from test
    </select>
</mapper>

2、创建Service文件

image.png

  • TestService
public interface TestService {

    int count();
}
  • TestServiceImpl
@Service
public class TestServiceImpl implements TestService {

    @Autowired(required = false)
    private TestMapper testMapper;

    @Override
    public int count() {
        return testMapper.count();
    }
}

3、 创建Controller文件

image.png

  • TestController
@RestController
@Slf4j(topic = "c.TestController")
public class TestController {
    @Autowired
    private TestService testService;

    @GetMapping("test")
    public void test() {
        int count = testService.count();
        log.debug("count =====> {}",count);
    }
}

④:添加Tomcat

  • 添加Tomcat image.png

  • 配置相关信息

image.png

image.png

image.png

⑤:运行测试

image.png

三、整合freemarker模板引擎

①:引入依赖

<!--freemarker-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.23</version>
</dependency>

②:添加配置

image.png

  • servlet-common.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置映射器与适配器 -->
    <mvc:annotation-driven/>

    <!--配置视图解析器  prefix-->
    <bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
        <property name="suffix" value=".html"/>
        <property name="contentType" value="text/html; charset=utf-8"/>
    </bean>

    <!--freemarker的配置-->
    <bean id="freeMarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <!--freemarker模板路径位置-->
        <property name="templateLoaderPath" value="/WEB-INF/"/>
        <!--一些其他设置-->
        <property name="freemarkerSettings">
            <props>
                <!--  本地化设置 -->
                <prop key="locale">zh_CN</prop>
                <!--解决freemarker渲染页面乱码-->
                <prop key="default_encoding">UTF-8</prop>
            </props>
        </property>
    </bean>
</beans>
  • servlet-admin.xml
<!--引入公共的配置-->
<import resource="classpath:servlet-common.xml"/>
  • TestController中添加以下方法进行访问

  • @RestController修改为@Controller

@Controller
//@RestController
@Slf4j(topic = "c.TestController")
public class TestController {
    @Autowired
    private TestService testService;
    
    @GetMapping("test2")
    public String test2() {
        return "/admin/test/index";
    }
}
  • 测试

image.png

四、集成数据库版本控制flyway

①:介绍

Flyway是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。用通俗的话讲,Flyway可以像Git管理不同人的代码那样,管理不同人的sql脚本,从而做到数据库同步。

②:引入依赖

<!--flyway-->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>4.1.0</version>
</dependency>

③:配置和整合

image.png

  • applicationContext-datasource.xml
<!--配置flyway-->
<bean id="flyway" class="org.flywaydb.core.Flyway" init-method="migrate" depends-on="dataSource">
    <property name="dataSource" ref="dataSource"/>
    <property name="locations" value="migration"/>
    <property name="baselineOnMigrate" value="true"/>
</bean>
  • 创建 V1__Base_version.sql 文件
-- ----------------------------
--  Table structure for cms_user_primary 用户主表
-- ----------------------------
CREATE TABLE cms_user_primary (
   create_time timestamp  not null  default CURRENT_TIMESTAMP,
   update_time timestamp  not null default '0000-00-00 00:00:00',
    id int(11) NOT NULL AUTO_INCREMENT primary key,
    username varchar(50) not null comment '用户名',
    password varchar(64) not null comment '用户密码,MD5加密或sha256散列加密',
   salt varchar(64) not null comment '密码盐',
    email varchar(50) not null default '' comment '邮箱',
    login_count int(10) not null default 0 comment '登陆次数',
    UNIQUE KEY user_name_unique (username) USING BTREE,
    UNIQUE KEY user_email_unique (email) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO cms_user_primary (id, create_time,username, password, salt, email, login_count) VALUES (1, '2019-06-14 11:30:58',  'admin', 'e298f9b29585da289080ffebb32e6f931b52a61195bcf6246d3e0f24654897eb', '6e4abc9695661ce11f442eaea3cb6540', 'abc@126.com', 0);
INSERT INTO cms_user_primary (id, create_time, username, password, salt, email, login_count) VALUES (2, '2019-06-14 11:30:58',  'administrator', 'e298f9b29585da289080ffebb32e6f931b52a61195bcf6246d3e0f24654897eb', '6e4abc9695661ce11f442eaea3cb6540', 'def@126.com', 0);

④:运行测试

  • 运行后自动创建了两张表

image.png

  • 数据添加成功

image.png

image.png

五、添加登录页面和IDEA自动编译

image.png

①:添加副表

注意:先删除cms库中cms_user_primary表和schema_version

  • V1__Base_version.sql文件中添加以下SQL语句
-- ----------------------------
--  Table structure for cms_user  用户副表
-- ----------------------------
CREATE TABLE cms_user
(
    create_time       timestamp    not null default CURRENT_TIMESTAMP,
    update_time       timestamp    not null default '0000-00-00 00:00:00',
    id                int(11)      NOT NULL AUTO_INCREMENT primary key,
    group_id          int          null comment '会员组ID',
    username          varchar(50)  not null comment '用户名',
    email             varchar(50)  not null default '' comment '邮箱',
    `rank`             int                   default '0' not null comment '管理员级别',
    is_admin          tinyint(1)   not null default 0 comment '是否后台用户 0不是 1是',
    is_super          tinyint(1)   not null default 0 comment '是否超级管理员 0不是 1是',
    is_self_admin     tinyint(1)            default '0' not null comment '是否受限管理员 是否只管理自己的数据 0:否 1:是',
    register_time     timestamp    not null comment '注册时间',
    register_ip       varchar(50)           default '127.0.0.1' not null comment '注册IP',
    login_count       int(10)      not null default 0 comment '登陆次数',
    upload_total      bigint                default '0' not null comment '上传总大小',
    upload_size       int                   default '0' not null comment '上传大小',
    is_viewonly_admin tinyint(1)            default '0' not null comment '是否只读管理员 0:否 1:是',
    upload_date       timestamp    null comment '上传日期',
    session_id        varchar(200) not null default '' comment '用户session id',
    status            tinyint(1)            default '1' not null comment '状态',
    deleted           tinyint(1)            default '1' not null comment '是否已删除 0:删除 1正常',
    last_login_ip     varchar(30)  not null default '' comment '最后登录ip',
    last_login_time   timestamp    not null default '0000-00-00 00:00:00' comment '最后登录时间',
    UNIQUE KEY user_name_unique (username) USING BTREE,
    UNIQUE KEY user_email_unique (email) USING BTREE
) ENGINE = InnoDB  DEFAULT CHARSET = utf8mb4;
INSERT INTO cms_user (id, create_time, group_id, username, email, `rank`, is_admin, is_super, is_self_admin,
                      register_time, register_ip, login_count, upload_total, upload_size, is_viewonly_admin,
                      upload_date, session_id, last_login_ip)
VALUES (1, '2019-06-14 11:30:58', 1, 'admin', 'abc@126.com', 1, 1, 1, 0, '2019-06-14 11:30:58', '127.0.0.1', 0, 0,
        0, 0, null, '', '');
INSERT INTO cms_user(id, create_time,  group_id, username, email, `rank`, is_admin, is_self_admin,
                     register_time, register_ip, login_count, upload_total, upload_size, is_viewonly_admin, upload_date,
                     session_id, last_login_ip)
VALUES (2, '2019-06-14 11:30:58',  1, 'administrator', 'def@126.com', 1, 1, 0, '2019-06-14 11:30:58', '127.0.0.1',
        0, 0, 0, 0, null, '', '');

②:添加登录页面

image.png

    1. 将login-1.html页面复制到admin文件夹中并重命名为login.html
    1. 登录密码为a1234567

③:添加页面效果

    1. 加添layui

image.png 修改login.html页面12\color{#00FF00}{修改login.html页面 12行}

<link rel="stylesheet" href="/admin/layui/css/layui.css" media="all">
    1. 添加jq.js文件

image.png 修改login.html页面6364\color{#00FF00}{修改login.html页面 63和64行}

<script src="/common/js/jq.js" charset="utf-8"></script>
<script src="/admin/layui/layui.js" charset="utf-8"></script>
    1. 添加 jquery.particleground.min.js 文件

image.png

修改login.html页面65\color{#00FF00}{修改login.html页面 65行}

<script src="/admin/js/jquery.particleground.min.js" charset="utf-8"></script>

④:创建LoginController

@Controller
public class LoginController {

    @GetMapping("login.do")
    public String toLogin() {
        return "/admin/login";
    }
}

⑤:运行测试

image.png

    1. 问题分析:

image.png

    1. 解决问题:

执行Maven的clean命令后重新启动项目后再访问

image.png

⑥:开启IDEA自动编译

image.png

  • 快捷键Ctrl+Alt+Shift+/

image.png

image.png

六、集成redis并配置

①:导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.6.2.RELEASE</version>
</dependency>

②:添加applicationContext-redis.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--jedis连接池需要-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 连接池中最多可空闲maxIdle个连接 ,这里取值为20,表示即使没有数据库连接时依然可以保持20空闲的连接,而不被清除,随时处于待命状态。 -->
        <property name="maxIdle" value="20"/>
        <!-- 最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常 -->
        <property name="maxWaitMillis" value="1000"/>
        <!-- 在获取连接的时候检查有效性 -->
        <property name="testOnBorrow" value="true"/>
    </bean>

    <!-- 连接池配置,类似数据库连接池 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="poolConfig" ref="poolConfig"/>
        <property name="usePool" value="true"/>
        <property name="hostName" value="127.0.0.1"/>
        <property name="database" value="0"/>
    </bean>

    <!--使用spring为我们配置好的redisTemplate来操作redis-->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
<!--        <property name="keySerializer" ref="stringRedisSerializer"/>-->
<!--        <property name="valueSerializer" ref="stringRedisSerializer"/>-->
    </bean>
    <!--redis序列化器 不使用jdk自带的 那个影响阅读-->
<!--    <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>-->
</beans>

③:引入到applicationContext.xml文件中

<import resource="classpath:applicationContext-redis.xml"/>

④:启动redis

image.png

⑤:编写测试代码

  • TestController
@Autowired(required = false)
private RedisTemplate<String,String> redisTemplate;

@GetMapping("test3")
public void test3() {
    ValueOperations<String, String> ops = redisTemplate.opsForValue();
    ops.set("name","张三");
    log.debug("name = {}",ops.get("name"));
}

⑥:运行测试

image.png

image.png

  • 修改使用自定义序列化器后测试

取消applicationContext-datasource.xml 中 28、29、32行的注释

image.png

七、集成kaptcha验证码,优雅的关闭流

①:导入依赖 kaptcha

1. 排除kaptcha中的servlet-api包

排除原因:tomcat自带了servlet-api包

1.tomcat-servlet-api和servlet-api的区别:

tomcat-servlet-api是tomcat封装的servlet,tomcat-servlet-api包装了servlet-api类

image.png

2.只在编译和测试中使用tomcat-servlet-api:

image.png

3.解决方法

给tomcat-servlet-api包添加provided属性

<!--servlet-->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-servlet-api</artifactId>
    <version>${servletVersion}</version>
    <scope>provided</scope>
</dependency>

image.png

2.排除依赖简便方法

image.png

image.png

4.kaptcha包中屏蔽servlet-api:

<!--kaptcha-->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>${kaptchaVersion}</version>
    <exclusions>
        <exclusion>
            <artifactId>javax.servlet-api</artifactId>
            <groupId>javax.servlet</groupId>
        </exclusion>
    </exclusions>
</dependency>

②:添加配置文件

  • applicationContext-configuration.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
    <!--图片验证码-->
    <bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
        <property name="config">
            <bean class="com.google.code.kaptcha.util.Config">
                <constructor-arg>
                    <props>
                        <prop key="kaptcha.border">yes</prop>
                        <prop key="kaptcha.border.color">105,179,90</prop>
                        <prop key="kaptcha.textproducer.font.color">blue</prop>
                        <prop key="kaptcha.image.width">200</prop>
                        <prop key="kaptcha.image.height">50</prop>
                        <prop key="kaptcha.textproducer.font.size">35</prop>
                        <prop key="kaptcha.session.key">code</prop>
                        <prop key="kaptcha.textproducer.char.length">4</prop>
                        <prop key="kaptcha.textproducer.font.names">宋体,楷体,微软雅黑</prop>
                    </props>
                </constructor-arg>
            </bean>
        </property>
    </bean>

</beans>

③:引入到applicationContext.xml文件中

<import resource="classpath:applicationContext-configuration.xml"/>

④:编写测试代码

@Autowired
private Producer captchaProducer;

@GetMapping("captcha.do")
public void toCaptcha(HttpServletResponse response) {
    // 生成验证码字符
    String text = captchaProducer.createText();
    // 根据生成的验证码字符生成图片
    BufferedImage image = captchaProducer.createImage(text);

    try(ServletOutputStream outputStream = response.getOutputStream()){
        ImageIO.write(image, "jpg", outputStream);
    }catch (IOException e) {
        e.printStackTrace();
    }
}

⑤:运行测试

image.png

八、项目集成日志系统

①:日志介绍

1. 介绍

常用的日志组件实现有哪些:

image.png

2. 日志门面

  1. Apache Commons Logging:

Apache(Jakarta)Commons Logging (JCL)提供的是一个日志(Log)接口(interface),同时兼顾轻量级和不依赖于具体的日志实现工具。它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。用户被假定已熟悉某种日志实现工具的更高级别的细节。JCL提供的接口,对其它一些日志工具,包括Log4J,AvalonLogKit,and JDK 1.4等,进行了简单的包装,可以让应用程序在运行时,直接将JCL APl打点的日志适配到对应的日志实现工具中。

在sun开发logger 前,apache 项目已经开发了功能强大的 log4j日志工具,并向 sun推荐将其纳入到jdk 的—部分,可是sun拒绝了apache的提议,sun 后来自己开发了一套记录日志的工具,即JUL。可是现在的开源项目都使用的是log4j,log4j 已经成了事实上的标准,但由于又有一部分开发者在使用sunlogger,因此 apache才推出ApacheCommons Logging,使得我们不必关注我们正在使用何种日志工具。

JCL动态查找(绑定)日志组件原理:

image.png

JCL为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根据指定顺序查找classPath是否存在相应日志的实现。如果JCL运行时没找到任何一种第三方的日志实现,则就用 jdk14自带的java.util.logging(JUL)。假如你的maven工程pom.xml里加入了log4j的依赖,运行时JCL找到即可动态绑定。

Spring的日志就是采用JCL,解决了应用程序和框架日志不统一的问题。动态去寻找(应用程序配置的)日志体系的实现。

但JCL方式也有不足,不能把所有日志的实现都包含。具体可以看commons-logging框架的LoggerFactory和LoggerFactoryImpl,里面硬编码了数组,不包含log4j2和logback的最新实 现。

org.apache.commons.logging.impl.LogFactoryImpl见下图:

image.png

通过看JCL的源码,jcl为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根 据指定顺序查找classPath是否存在相应的实现,查找顺序就是数组元素的顺序。如果一个应用当中有多个classLoader。比如OSGI规定了每个模块都有其独立的ClassLoader。这种机制保证了插件互相独立,同时也限制了JCL在OSGi中的正常使用。这时出现了slf4j基于静态绑定的方式来解决了这个问题。

  1. SLF4J:

SLF4J全称Simple LoggingFacade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过FacadePattern门面模式对外提供一些Java Logging APl。这些对外提供的核心API其实就是一些接口以及一个LoggerFactory的工厂类。

在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定想用的Log库。

按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Apache Commons Logging。即使以后又出现更新的其他日志组件,也能完全适应。

SLF4J静态绑定日志组件原理:

image.png

image.png

使用SLF4J时,如果你需要使用某一种日志实现,那么你选择相对应的SLF4J的桥接包即可。比如使用log4j日志组件,就选slf4j-log4j12桥接包,业务中就可以使用log4j进行底层日志输出。

SLF4J提供的桥接包:
• slfj-log4j12.jar(表示桥接 log4j)
• slf4j-jdk14.jar(表示桥接jdk Looging)
• sIf4j-jcl.jar(表示桥接 jcl)
•log4j-slf4j-impl(表示桥接log4j2)
logback-classic(表示桥接 logback)

SLF4J提供了统一的记录日志的接口(LoggerFactory),只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

logback是slf4j-api的天然实现,不需要桥接包就可以使用。与commons loging(JCL)不同的是其采用在classPath加入桥接jar包来表示具体采用哪种实现(静态绑定).

总结:

  • JCL方式的commons-logging是动态查找绑定。

  • SLF4J是静态绑定,需要加桥接包。如slf4j-log4j2。

②:集成slf4j,排除commons-logging依赖

1.放弃spring自带的apache commons-logging依赖:

从Spring官方文档中可以看出,它其实也只有spring-core 这个模块依赖了commons-logging,所以,替换掉它就可以了.

以使用SLF4j框架来替换为例,具体的替换步骤如下:

  • 从spring-core的依赖中排除掉commons-logging 依赖。
  • 添加jcl-over-slf4j依赖。
  • 添加slf4j以及 log4j 依赖

一个是排除掉JCL,第二个是要架起JCL与slf4j的桥梁,其实这里slf4j巧妙的将spring-core中用到的commons-logging的类或接口,在jcl-over-slf4j中写了一份同名的,但在具体的方法或 接囗等实现类中,再去具体找其他日志实现框架,它起到了一个日志门面的作用。

第一步:从spring-core的依赖中排除掉commons-logging 依赖

<!--springMvc-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${springVersion}</version>
    <exclusions>
        <exclusion>
            <artifactId>commons-logging</artifactId>
            <groupId>commons-logging</groupId>
        </exclusion>
    </exclusions>
</dependency>

第二步: 添加jcl-over-slf4j依赖

<!--jcl-over-slf4j依赖-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
</dependency>

第三步: 添加slf4j以及 log4j 依赖

  1. 将spring-data-redis的版本匹配为spring的版本为4.3.20.RELEASE:
  • 修改spring-data-redis的版本
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.16.RELEASE</version>
</dependency>
  1. spring-data-redis依赖中已经引入了slf4j-api包,所以slf4j-api包可以暂时不引入

image.png

<!--        <dependency>-->
<!--            <groupId>org.slf4j</groupId>-->
<!--            <artifactId>slf4j-api</artifactId>-->
<!--            <version>1.7.30</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
  1. jcl-over-slf4j可以不用在引入了

image.png

        <!--jcl-over-slf4j依赖-->
<!--        <dependency>-->
<!--            <groupId>org.slf4j</groupId>-->
<!--            <artifactId>jcl-over-slf4j</artifactId>-->
<!--            <version>1.7.25</version>-->
<!--        </dependency>-->

③:启动项目测试

项目成功启动没有问题

④:查看日志的输出目录

image.png

image.png

⑤:在项目中使用slf4j记录日志

1. 日志级别

日志级别描述
OFF关闭:最高级别,不输出日志。
FATAL致命:输出非常严重的可能会导致应用程序终止的错误
ERROR错误:输出错误,但应用还能继续运行。
WARN警告:输出可能潜在的危险状况。
INFO信息:输出应用运行过程的详细信息。
DEBUG调试:输出更细致的对调试应用有用的息。
TRACE跟踪:输出更细致的程序运行轨迹。
ALL所有:输出所有级别信息。

Log4j官方建议实际实用的话,Log4j建议只使用四个级别,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG.

2. 什么时候记录日志?

  • 系统初始化:系统或者服务的启动参数。核心模块或者组件初始化过程中往往依赖一些关键配置,根据参数不同会提供不一样的服务。务必在这里记录 INFO 日志,打印出参数以及启动完成态服务表述。

  • 编程语言提示异常:如今各类主流的编程语言都包括异常机制,业务相关的流行框架有完整的异常模块。这类捕获的异常是系统告知开发人员需要加以关注的,是质量非常高的报错。应当适当记录日志,根据实际结合业务的情况使用WARN 或者 ERROR 级别。

  • 业务流程预期不符:除开平台以及编程语言异常之外,项目代码中结果与期望不符时也是日志场景之一,简单来说所有流程分支都可以加入考虑。取决于开发人员判断能否容忍情形发生。常见的合适场景包括外部参数不正确,数据处理问题导致返回码不在合理范围内等等

  • 系统核心角色,组件关键动作:系统中核心角色触发的业务动作是需要多加关注的,是衡量系统正常运行的重要指标,建议记录INFO 级别日志,比如电商系统用户从登录到下单的整个流程;微服务各服务节点交互;核心数据表增删改;核心组件运行等等,如果日志频度高或者打印量特别大,可以提炼关键点INFO 记录,其余酌情考虑DEBUG 级别

  • 第三方服务远程调用:微服务架构体系中有一个重要的点就是第三方永远不可信,对于第三方服务远程调用建议打印请求和响应的参数,方便在和各个终端定位问题,不会因为第三方服务日志的缺失变得手足无措

3. log4j配置文件

log4j.rootLogger = [ level ] , appenderName, appenderName ,…

把指定级别及级别以上的日志信息输出到指定的一个或者多个位置. appenderName:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。

我们把INFO及INFO层级以及以上的信息输出到Console和File;

即输出到控制台和本地硬盘文件:

log4j.rootLogger=INFO,-Console ,File

#Console (输出到控制台)
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t]%-5p[%]-%m%n

#File(输出到文件)
log4j.appender.File = org.apache.log4j.FileAppender
log4j.appender.File.File = d://log4j2.log
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =%d [%t]%-5p[%]-%m%n

4. 项目中配置log4j.properties

log4j.rootLogger=INFO,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.appender.stdout.Encoding=UTF-8

⑥:Lombok的使用

Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。

Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。

1. 引入jar包:

<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

Lombok的scope=provided,说明它只在编译阶段生效,不需要打入包中。事实正是如此,Lombok在编译期将带有Lombok注解的Java文件正确编译为完整的Class文件.

2.添加IDE工具对Lombok的支持:

1.点击File--Settings设置界面,安装Lombok插件:

image.png

2.点击File--Settings设置界面,开启AnnocationProcessors:

image.png

九、本地项目同步到github上

有时候我们需要在本地创建好项目后,然后才在github上创建项目。也可能是忘了。

①:使用IDEA创建本地仓库

image.png

image.png

创建成功后代码都变成了红色

image.png

image.png

②:创建远程仓库并推送本地代码

1. 安装ignore插件

image.png

2. 创建GitHub远程仓库

image.png

3. IDEA与远程仓库关联

image.png

image.png

  • 可以创建一个30天免费的密令

image.png

image.png

image.png

4. 排除不需要的文件并提交到本地仓库

在项目根目录下创建.gitignore文件

  • .gitignore
# Default ignored files
/shelf/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

#忽略所有.svn目录
.svn/
#忽略所有target目录
target/
*/target
#忽略所有.idea目录
.idea/
/.idea
#忽略所有.iml文件
*.iml

.project
.settings/
.classpath
logs
/classes/
*/logs
*.log
*/*.log
out
*/out
  • 添加 image.png
  • 提交

image.png

  • 推送(也可以提交 & 推送)

image.png

image.png

十、项目中集成shiro

①:完善登录页面验证码图片功能

1. 验证码图片正常显示

修改 login.html文件50行 验证码图片路径

<img id="captchaPic" src="/admin/cms/captcha.do">

图片加载成功 image.png

2. 点击图片刷新验证码和手停样式

修改 login.html文件49 和 50行 验证码图片路径

<div class="captcha-img" style="cursor: pointer">

<img id="captchaPic" src="/admin/cms/captcha.do" onclick="this.src='/admin/cms/captcha.do?random='+Math.random()">

点击图片刷新成功

image.png

②:集成ehCache

1. ehCache介绍

EhCache是一个纯Java的进程内缓存框架,具有快速、精干等特点,Ehcache是一种广泛使用的开 源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。

2. 导入依赖

<!--ehcache-->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.6</version>
</dependency>

3.添加ehCache.xml:

resources目录下创建ehcache.xml文件 image.png

<?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
    <!--
           diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
           user.home – 用户主目录
           user.dir  – 用户当前工作目录
           java.io.tmpdir – 默认临时文件路径
    -->
    <diskStore path="java.io.tmpdir/cms/ehcache"/>

    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <defaultCache
            maxElementsInMemory="10000"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>

    <!--菜单缓存-->
    <cache
            name="permissionCache"
            maxEntriesLocalHeap="10000"
            maxEntriesLocalDisk="1000"
            eternal="false"
            diskSpoolBufferSizeMB="30"
            timeToIdleSeconds="300"
            timeToLiveSeconds="6000"
            memoryStoreEvictionPolicy="LFU"
            transactionalMode="off">
    </cache>

    <!--授权信息缓存-->
    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <!--
        默认缓存配置,
        以下属性是必须的:
            name :cache的标识符,在一个CacheManager中必须唯一。
            maxElementsInMemory : 在内存中缓存的element的最大数目。
            maxElementsOnDisk : 在磁盘上缓存的element的最大数目。
            eternal : 设定缓存的elements是否有有效期。如果为true,timeouts属性被忽略。
            overflowToDisk : 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上。
        以下属性是可选的:
             timeToIdleSeconds : 缓存element在过期前的空闲时间。
             timeToLiveSeconds : 缓存element的有效生命期。
             diskPersistent : 在VM重启的时候是否持久化磁盘缓存,默认是false。
             diskExpiryThreadIntervalSeconds : 磁盘缓存的清理线程运行间隔,默认是120秒.
             memoryStoreEvictionPolicy : 当内存缓存达到最大,有新的element加入的时候,
                移除缓存中element的策略。默认是LRU,可选的有LFU和FIFO
    -->
</ehcache>

③:集成shiro

ApacheShiro是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多。本教程只介绍基本的 Shiro 使用,不会过多分析源码等,重在使用。

1. 引入依赖

<!--shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.2</version>
</dependency>
<!-- shiro的缓存包 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.3.2</version>
</dependency>
<!-- shiro与Spring的集成包 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

2. 配置web.xml添加以下内容

<!--shiro过滤器拦截所有请求-->
<filter>
  <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>

3. 创建realm

Realm:域,Realm充当了Shiro 与应用安全数据间的“桥梁"或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用 户及其权限信息.

创建com.it.portal.security.realm.UsernamePasswordCaptchaRealm:

public class UsernamePasswordCaptchaRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

4. 创建authc登陆认证拦截器:

com.it.portal.security.filter.CmsAuthenticationFilter

public class CmsAuthenticationFilter extends FormAuthenticationFilter{
}

5. 自定义user用户已登录拦截器:

com.it.portal.security.filter.CmsUserFilter

@Getter
@Setter
public class CmsUserFilter extends UserFilter {
    private String  adminLoginUrl;
    private String adminPrefix;
    
}

6.添加applicatipnContext-shiro.xml:

resources目录下创建applicatipnContext-shiro.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
    <!--配置realm-->
    <bean id="usernamePasswordCaptchaRealm" class="com.it.portal.security.realm.UsernamePasswordCaptchaRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--配置加密方式-->
                <property name="hashAlgorithmName" value="SHA-256"/>
                <!--散列迭代次数-->
                <property name="hashIterations" value="1024"/>
                <!--表示是否存储散列后的密码为16进制,需要和生成密码时的一样,默认是base64-->
                <!--此处最需要注意的就是HashedCredentialsMatcher的算法需要和生成密码时的算法一样。
                另外HashedCredentialsMatcher会自动根据AuthenticationInfo的类型是否是SaltedAuthenticationInfo来获取credentialsSalt盐-->
                <property name="storedCredentialsHexEncoded" value="true"/>
            </bean>
        </property>
        <property name="authorizationCachingEnabled" value="true"/>
        <property name="authorizationCacheName" value="authorizationCache"/>
    </bean>

    <!-- 开启spring缓存区别于shiro的缓存 -->
    <cache:annotation-driven cache-manager="springCacheManager"/>
    <bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
        <property name="shared" value="true"/>
    </bean>

    <!-- 缓存管理器 -->
    <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

    <!--安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--将自定义realm注入到securityManager-->
        <property name="realm" ref="usernamePasswordCaptchaRealm"/>
        <property name="cacheManager" ref="shiroCacheManager"/>
    </bean>

    <!--生命周期处理-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!--自定义authc登陆认证拦截器-->
    <bean id="cmsAuthenticationFilter" class="com.it.portal.security.filter.CmsAuthenticationFilter"/>

    <!--自定义user用户已登录拦截器-->
    <bean id="cmsUserFilter" class="com.it.portal.security.filter.CmsUserFilter">
        <property name="adminLoginUrl" value="/admin/cms/login.do"/>
        <property name="adminPrefix" value="/admin/cms/"/>
    </bean>

    <!--shiro拦截器-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--注入安全管理器-->
        <property name="securityManager" ref="securityManager"/>
        <!--登录链接-->
        <property name="loginUrl" value="/login.jspx"/>
        <!--成功登录链接-->
        <property name="successUrl" value="/"/>
        <!--自定义filter-->
        <property name="filters">
            <map>
                <entry key="authc" value-ref="cmsAuthenticationFilter"/>
                <entry key="user" value-ref="cmsUserFilter"/>
            </map>
        </property>
        <!--过滤链定义 anon匿名 authc登录认证  user用户已登录 logout退出filter-->
        <property name="filterChainDefinitions">
            <value>
                /login.jspx = authc
                **.jspx = anon
                **.jhtml = anon
                /member/** = user
                /admin/cms/captcha.do = anon
                /admin/cms/content/** = anon
                /admin/cms/error.do = anon
                /admin/cms/login.do = authc
                /admin/cms/logout.do = logout
                /admin/cms/** = user
            </value>
        </property>
    </bean>
</beans>

7. 引入到applicationContext.xml文件中

<import resource="classpath:applicationContext-shiro.xml"/>

8. 启动项目测试

image.png

④:登录验证

经过上面测试发现访问登录地址后会自动跳转到http://localhost/login.jspx这个页面

原因:

FormAuthenticationFiltershiro提供的登录的filter,如果用户未登录,即AuthenticatingFilter中的isAccessAllowed判断了用户未登录,则会调用onAccessDenied方法做用户登录操作。若用户请求的不是登录地址,则跳转到登录地址,并且返回false直接终止filter链。若用户请求的是登录地址,若果是post请求则进行登录操作,由AuthenticatingFilter中提供的 executeLogin方法执行。否则直接通过继续执行flter链,并最终跳转到登录页面(因为用户请求的就是登录地址,若不是登录地址也会重定向到登录地址)

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    if (this.isLoginRequest(request, response)) {
        if (this.isLoginSubmission(request, response)) {
            if (log.isTraceEnabled()) {
                log.trace("Login submission detected.  Attempting to execute login.");
            }
            return this.executeLogin(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Login page view.");
            }
            return true;
        }
    } else {
        if (log.isTraceEnabled()) {
            log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
        }
        this.saveRequestAndRedirectToLogin(request, response);
        return false;
    }

1. CmsAuthenticationFilter类中重写isLoginRequest方法

public class CmsAuthenticationFilter extends FormAuthenticationFilter{
    @Override
    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
        return super.isLoginRequest(request, response) ||
                this.pathsMatch("/admin/cms/login.do", request);
    }
}

2. 运行项目测试

  • 可以正常的访问

image.png

十一、配置项目全局绝对路径

①:获取项目war包并在Tomcat中运行

  1. 选择部署war包

image.png

  1. 运行项目在编译目录下拿到war包

拿到war包后将war包复制到Tomcat的Webapps目录下

image.png

  1. 启动Tomcat,并访问测试

image.png

访问:http://localhost/admin/cms/login.do

image.png

②:配置项目全局的绝对路径

使用绝对路径可以避免很多因为引用路径上带来的问题:

继承freemarker视图解析类org.springframework.web.servlet.view.freemarker.FreeMarkerView,重写exposeHelpers方法,在spring里配置自己的freemarker的视图解析器,在模板中就可以通过$(basePath)获取:

创建:com.it.portal.freemarker.CmsViewResolver

@Slf4j
public class CmsViewResolver extends FreeMarkerView {
    private static final String ADMIN_PATH = "/admin/cms/";
    
    @Override
    protected void exposeHelpers(Map<String, Object> model, HttpServletRequest request) throws Exception {
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        String servletPath = request.getServletPath();
        //就认为是后台请求路径
        if(requestURI.contains(ADMIN_PATH)){
            model.put("adminPath",contextPath+servletPath);
        }
        model.put("basePath",contextPath);
    }
}

③:两个重要的方法:

getContextPath():获取项目的根路径

getServletPath():获取能够与web.xml中的<servlet-mapping>标签中"url-pattern”中匹配的路径,注意是完全匹配的部分,*的部分不包括

getRequestURI() 返回除去host(域名或者ip)部分的路径

④:引入commons-lang3 jar包:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${commonsLang3Version}</version>
</dependency>

⑤:添加配置: 在servlet-common.xml中添加配置

<!--配置视图解析器  prefix-->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="suffix" value=".html"/>
    <property name="contentType" value="text/html; charset=utf-8"/>
    <property name="viewClass" value="com.it.portal.freemarker.CmsViewResolver"/>
</bean>

⑥:修改login.html

更改login.html中 12、63~65、50 行代码

<link rel="stylesheet" href="${basePath}/admin/layui/css/layui.css" media="all">
<script src="${basePath}/common/js/jq.js" charset="utf-8"></script>
<script src="${basePath}/admin/layui/layui.js" charset="utf-8"></script>
<script src="${basePath}/admin/js/jquery.particleground.min.js" charset="utf-8"></script>
<img id="captchaPic" src="${adminPath}/captcha.do" onclick="this.src='${adminPath}/captcha.do?random='+Math.random()">

⑦:运行项目在编译目录下拿到war包

拿到war包后将war包复制到Tomcat的Webapps目录下

image.png

  1. 访问原本的路径:http://localhost/admin/cms/login.do

image.png

  1. 加上cms-portal后再访问:http://localhost/cms-portal/admin/cms/login.do

image.png

十二、集成jrebel热启动

①:设置Tomcat

image.png

image.png

②:设置IDEA

image.png

  • 快捷键Ctrl+Alt+Shift+/

image.png

image.png

③:启动项目测试

  • 更改资源内容 image.png

  • 更改Java代码

image.png

④:安装 jrebel 插件

1. 安装

image.png

  • 安装成功后IDEA上面多了两个小火箭

image.png

2. 激活jrebel

在idea中如下步骤点击:

File ——> Setting... ——> JRebel ——> Activate now

image.png

在线GUID地址:www.guidgen.com/

注册地址填写激活网址 + 生成的GUID,邮箱随便填写,然后直接激活即可

激活网址列表,尽量用排序靠前的

jrebel-license.jiweichengzhu.com/

jrebel.qekang.com/

image.png

离线使用,过期后重复操作即可

image.png

离线一次能使用很久,大概是6个月后过期

image.png

⑤:使用JRebel启动项目

image.png

修改代码后不用重新启动项目就可以完成更改操作 image.png

⑥:Mybatis热加载Mappper.xml

开发的时候,写Mybatis Mapper.xml文件的时候,每次修改SQL都需要重启服务,感觉十分麻烦,如何实现Mapper.xml热加载:

能在修改Mapper.xml之后重新加载Mybatis,开发的时候可以用一下

创建 com.it.portal.reload.XMLMapperLoader 类

package com.it.portal.reload;


import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @program: 01_cms
 * @description:
 * @author: Coke
 * @create: 2022-12-05 14:51
 **/

@Component
@Slf4j
public class XMLMapperLoader implements DisposableBean, InitializingBean, ApplicationContextAware {

    private ConfigurableApplicationContext context = null;
    private transient String basePackage = null;
    private HashMap<String, String> fileMapping = new HashMap<String, String>();
    private Scanner scanner = null;
    private ScheduledExecutorService service = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.context = (ConfigurableApplicationContext) applicationContext;

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        try {
            service = Executors.newScheduledThreadPool(1);
            // 获取xml所在包
            MapperScannerConfigurer config = context.getBean(MapperScannerConfigurer.class);
            config.setBasePackage("com/it/dao/mapper/mappers");
            Field field = config.getClass().getDeclaredField("basePackage");
            field.setAccessible(true);
            basePackage = (String) field.get(config);
            // 触发文件监听事件
            scanner = new Scanner();
            scanner.scan();
            service.scheduleAtFixedRate(new Task(), 5, 5, TimeUnit.SECONDS);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }

    class Task implements Runnable {
        @Override
        public void run() {
            try {
                if (scanner.isChanged()) {
                    System.out.println("*Mapper.xml文件改变,重新加载.");
                    scanner.reloadXML();
                    System.out.println("加载完毕.");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    @SuppressWarnings({"rawtypes"})
    class Scanner {
        private String[] basePackages;
        private static final String XML_RESOURCE_PATTERN = "**/*.xml";
        private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

        public Scanner() {
            basePackages = StringUtils.tokenizeToStringArray(XMLMapperLoader.this.basePackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        }

        public Resource[] getResource(String basePackage, String pattern) throws IOException {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + ClassUtils.convertClassNameToResourcePath(context.getEnvironment().resolveRequiredPlaceholders(
                    basePackage)) + "/" + pattern;
            return resourcePatternResolver.getResources(packageSearchPath);
        }

        public void reloadXML() throws Exception {
            SqlSessionFactory factory = context.getBean(SqlSessionFactory.class);
            Configuration configuration = factory.getConfiguration();
            // 移除加载项
            removeConfig(configuration);
            // 重新扫描加载
            for (String basePackage : basePackages) {
                Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN);
                if (resources != null) {
                    for (int i = 0; i < resources.length; i++) {
                        if (resources[i] == null) {
                            continue;
                        }
                        try {
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resources[i].getInputStream(),
                                    configuration, resources[i].toString(), configuration.getSqlFragments());
                            xmlMapperBuilder.parse();
                        } catch (Exception e) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + resources[i] + "'", e);
                        } finally {
                            ErrorContext.instance().reset();
                        }
                    }
                }
            }

        }

        private void removeConfig(Configuration configuration) throws Exception {
            Class<?> classConfig = configuration.getClass();
            clearMap(classConfig, configuration, "mappedStatements");
            clearMap(classConfig, configuration, "caches");
            clearMap(classConfig, configuration, "resultMaps");
            clearMap(classConfig, configuration, "parameterMaps");
            clearMap(classConfig, configuration, "keyGenerators");
            clearMap(classConfig, configuration, "sqlFragments");

            clearSet(classConfig, configuration, "loadedResources");

        }

        private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
            Field field = classConfig.getDeclaredField(fieldName);
            field.setAccessible(true);
            Map mapConfig = (Map) field.get(configuration);
            mapConfig.clear();
        }

        private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
            Field field = classConfig.getDeclaredField(fieldName);
            field.setAccessible(true);
            Set setConfig = (Set) field.get(configuration);
            setConfig.clear();
        }

        public void scan() throws IOException {
            if (!fileMapping.isEmpty()) {
                return;
            }
            for (String basePackage : basePackages) {
                Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN);
                if (resources != null) {
                    for (int i = 0; i < resources.length; i++) {
                        String multi_key = getValue(resources[i]);
                        fileMapping.put(resources[i].getFilename(), multi_key);
                    }
                }
            }
        }

        private String getValue(Resource resource) throws IOException {
            String contentLength = String.valueOf((resource.contentLength()));
            String lastModified = String.valueOf((resource.lastModified()));
            return new StringBuilder(contentLength).append(lastModified).toString();
        }

        public boolean isChanged() throws IOException {
            boolean isChanged = false;
            for (String basePackage : basePackages) {
                Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN);
                if (resources != null) {
                    for (int i = 0; i < resources.length; i++) {
                        String name = resources[i].getFilename();
                        String value = fileMapping.get(name);
                        String multi_key = getValue(resources[i]);
                        if (!multi_key.equals(value)) {
                            isChanged = true;
                            fileMapping.put(name, multi_key);
                        }
                    }
                }
            }
            return isChanged;
        }
    }

    @Override
    public void destroy() throws Exception {
        if (service != null) {
            service.shutdownNow();
        }
    }
}

\color{#00FF00}{}