cms项目(1)--Layui封装和登录功能实现
笔记中涉及资源:
提取码:Coke
一、cms系统多模块的构建
①:cms系统各模块的作用
| 模块名 | 模块作用 |
|---|---|
| cms-portal | 前台页面 控制器 |
| cms-service | service层 dto(接收前台数据) |
| cms-dao | dao层entity(数据库) enums mapper |
| cmS-context | 上下文层 承上启下 |
| cms-core | 核心基础功能 |
②:创建多模块
- 修改编码格式
③:如何让idea识别web项目
idea识别web项目有两个重要的条件:
-
- IDEA版本必须是ULTIMATE版本
-
- plugins里启用 Java EE:EJB,JPA,Servlets插件.
- 将对应模块设置为web模块
二、ssm框架整合
①:相关配置文件整合
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>
②:数据准备(创建数据库和表)
③:代码测试
根据需要自行导入包(各模块)之间的依赖
1、创建mapper文件
- 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文件
- 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文件
- 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
-
配置相关信息
⑤:运行测试
三、整合freemarker模板引擎
①:引入依赖
<!--freemarker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
②:添加配置
- 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";
}
}
- 测试
四、集成数据库版本控制flyway
①:介绍
Flyway是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。用通俗的话讲,Flyway可以像Git管理不同人的代码那样,管理不同人的sql脚本,从而做到数据库同步。
②:引入依赖
<!--flyway-->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>4.1.0</version>
</dependency>
③:配置和整合
- 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);
④:运行测试
- 运行后自动创建了两张表
- 数据添加成功
五、添加登录页面和IDEA自动编译
①:添加副表
注意:先删除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, '', '');
②:添加登录页面
-
- 将login-1.html页面复制到admin文件夹中并重命名为login.html
-
- 登录密码为a1234567
③:添加页面效果
-
- 加添layui
<link rel="stylesheet" href="/admin/layui/css/layui.css" media="all">
-
- 添加jq.js文件
<script src="/common/js/jq.js" charset="utf-8"></script>
<script src="/admin/layui/layui.js" charset="utf-8"></script>
-
- 添加 jquery.particleground.min.js 文件
<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";
}
}
⑤:运行测试
-
- 问题分析:
-
- 解决问题:
执行Maven的clean命令后重新启动项目后再访问
⑥:开启IDEA自动编译
- 快捷键Ctrl+Alt+Shift+/
六、集成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
⑤:编写测试代码
- 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"));
}
⑥:运行测试
- 修改使用自定义序列化器后测试
取消applicationContext-datasource.xml 中 28、29、32行的注释
七、集成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类
2.只在编译和测试中使用tomcat-servlet-api:
3.解决方法
给tomcat-servlet-api包添加provided属性
<!--servlet-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${servletVersion}</version>
<scope>provided</scope>
</dependency>
2.排除依赖简便方法
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();
}
}
⑤:运行测试
八、项目集成日志系统
①:日志介绍
1. 介绍
常用的日志组件实现有哪些:
2. 日志门面
- 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动态查找(绑定)日志组件原理:
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见下图:
通过看JCL的源码,jcl为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根 据指定顺序查找classPath是否存在相应的实现,查找顺序就是数组元素的顺序。如果一个应用当中有多个classLoader。比如OSGI规定了每个模块都有其独立的ClassLoader。这种机制保证了插件互相独立,同时也限制了JCL在OSGi中的正常使用。这时出现了slf4j基于静态绑定的方式来解决了这个问题。
- SLF4J:
SLF4J全称Simple LoggingFacade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过FacadePattern门面模式对外提供一些Java Logging APl。这些对外提供的核心API其实就是一些接口以及一个LoggerFactory的工厂类。
在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定想用的Log库。
按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Apache Commons Logging。即使以后又出现更新的其他日志组件,也能完全适应。
SLF4J静态绑定日志组件原理:
使用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 依赖
- 将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>
- spring-data-redis依赖中已经引入了slf4j-api包,所以slf4j-api包可以暂时不引入
<!-- <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>
- jcl-over-slf4j可以不用在引入了
<!--jcl-over-slf4j依赖-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>jcl-over-slf4j</artifactId>-->
<!-- <version>1.7.25</version>-->
<!-- </dependency>-->
③:启动项目测试
项目成功启动没有问题
④:查看日志的输出目录
⑤:在项目中使用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插件:
2.点击File--Settings设置界面,开启AnnocationProcessors:
九、本地项目同步到github上
有时候我们需要在本地创建好项目后,然后才在github上创建项目。也可能是忘了。
①:使用IDEA创建本地仓库
创建成功后代码都变成了红色
②:创建远程仓库并推送本地代码
1. 安装ignore插件
2. 创建GitHub远程仓库
3. IDEA与远程仓库关联
- 可以创建一个30天免费的密令
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
- 添加
- 提交
- 推送(也可以提交 & 推送)
十、项目中集成shiro
①:完善登录页面验证码图片功能
1. 验证码图片正常显示
修改 login.html文件50行 验证码图片路径
<img id="captchaPic" src="/admin/cms/captcha.do">
图片加载成功
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()">
点击图片刷新成功
②:集成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文件
<?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. 启动项目测试
④:登录验证
经过上面测试发现访问登录地址后会自动跳转到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. 运行项目测试
- 可以正常的访问
十一、配置项目全局绝对路径
①:获取项目war包并在Tomcat中运行
- 选择部署war包
- 运行项目在编译目录下拿到war包
拿到war包后将war包复制到Tomcat的Webapps目录下
- 启动Tomcat,并访问测试
②:配置项目全局的绝对路径
使用绝对路径可以避免很多因为引用路径上带来的问题:
继承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目录下
- 加上cms-portal后再访问:http://localhost/cms-portal/admin/cms/login.do
十二、集成jrebel热启动
①:设置Tomcat
②:设置IDEA
- 快捷键Ctrl+Alt+Shift+/
③:启动项目测试
-
更改资源内容
-
更改Java代码
④:安装 jrebel 插件
1. 安装
- 安装成功后IDEA上面多了两个小火箭
2. 激活jrebel
在idea中如下步骤点击:
File ——> Setting... ——> JRebel ——> Activate now
在线GUID地址:www.guidgen.com/
注册地址填写激活网址 + 生成的GUID,邮箱随便填写,然后直接激活即可
激活网址列表,尽量用排序靠前的
jrebel-license.jiweichengzhu.com/
离线使用,过期后重复操作即可
离线一次能使用很久,大概是6个月后过期
⑤:使用JRebel启动项目
修改代码后不用重新启动项目就可以完成更改操作
⑥: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();
}
}
}