仅作为记录,如有不妥,烦请大佬指出。
一:pom文件的引入,将框架所用到的jar包导入进来,刷新maven工程
注意:亲测所有依赖版本均兼容
创建时首先要注意是否版本兼容,因为如果不兼容,启动项目时可能会报一些错误(笔者踩过许多坑)
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--spring 配置文件创建--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.7.RELEASE</version> </dependency> <!--阿里json包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!--spring 核心--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.2.7.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> <!--mybatis核心--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency> <!--mysql相关包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <!-- 数据库连接池-druid方式 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency> <!--aop相关包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.5</version> </dependency> <!--日志相关包--> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-spring</artifactId> <version>0.1.5</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.10</version> </dependency> <!--lombok相关包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> <!--json--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.10.2</version> </dependency> <!-- 添加<scope>provided</scope>,因为provided表明该包只在编译和测试的时候用,所以,当启动tomcat的时候,就不会冲突了,完整依赖如下--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> <version>4.0.1</version> </dependency> <!--hibernate的vail框架--> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.0.Alpha1</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.1.Final</version> </dependency> <!-- jwt依赖包 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> <!-- redis依赖 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.2.RELEASE</version> </dependency> </dependencies>
二:创建spring所需的配置文件,这里我们命名为applicationContext(spring配置内容上下文):包含配置业务层(service)的扫描、redis配置文件、myatis配置文件、和mybatis配置文件中所需的配置器交给spring容器管理
1.配置扫描器,扫描service是具体将service业务层都交给spring容器去管理
<?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: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.springframewopeizrk.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--配置扫描器 扫描service业务层--> <context:component-scan base-package="com.atdp.dpinfo.service"/> <!--资源属性的配置器,将BeanFactory的里定义的内容放在一个以.propertis后缀的文件中,将上下文(配置文 件)中的属性值放在另一个单独的标准java Properties文件中去--> <bean id="configurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > <property name="locations"> <list> <!--将配置文件引入--> <value>classpath:config.properties</value> <value>classpath:redis.properties</value> </list> </property> <property name="fileEncoding" value="UTF-8"></property> </bean> <!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 --> <context:component-scan base-package="com.atdp.dpinfo.service"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- spring 注解 扫描除了Controller之外的其他组件 --> <context:annotation-config/> <context:component-scan base-package="com.atdp.dpinfo.common.jwt"/> <!-- aop注解注入 --> <!-- <aop:aspectj-autoproxy/>--> <!--扫面mybatis,将mybatis整合到spring中--> <import resource="spring-mybatis.xml"/> <!--引入redis,扫描redis--> <import resource="applicationContext-redis.xml"/> </beans>
三:创建数据库连接池配置,因我们是测试阶段,并没有生产环境和开发环境之分,所以只创建config.properties,如果区分环境的,可多次创建此配置文件
注:需注意格式
#JDBC jdbc.driver= com.mysql.cj.jdbc.Driver jdbc.url= jdbc:mysql://xxxxxxxx:33xx/xxxxx?useSSL=true jdbc.username= xxx jdbc.password= xxxxxxxx #\u5B9A\u4E49\u521D\u59CB\u8FDE\u63A5\u6570 jdbc.initialSize=0 #\u5B9A\u4E49\u6700\u5927\u8FDE\u63A5\u6570 jdbc.maxActive=20 #\u5B9A\u4E49\u6700\u5927\u7A7A\u95F2 jdbc.maxIdle=20 #\u5B9A\u4E49\u6700\u5C0F\u7A7A\u95F2 jdbc.minIdle=1 #\u5B9A\u4E49\u6700\u957F\u7B49\u5F85\u65F6\u95F4 jdbc.maxWait=60000
四:创建mybatis配置文件,因都是交给spring整合的,命名为spring-mybatis:
1.将交给spring容器管理的连接池内容拿进
2.结合spring和mybatis,扫描mapper层中所有类
3.配置DAO接口所在包名,Spring会自动查找其下的类
<?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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 连接池 --> <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="${jdbc.initialSize}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${jdbc.maxActive}"/> <!-- 连接池最大空闲 --> <!-- <property name="maxa" value="${jdbc.maxIdle}" />--> <!-- 连接池最小空闲 --> <property name="minIdle" value="${jdbc.minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${jdbc.maxWait}" /> </bean> <!-- 结合Spring和Mybatis --> <bean id = "sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自动扫描mapping.xml文件 --> <property name = "mapperLocations" value="classpath:mapper/*.xml" /> </bean> <!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name = "basePackage" value="com.atdp.dpinfo.mapper" /> <property name = "sqlSessionFactoryBeanName" value = "sqlSessionFactory" /> </bean> <!-- 定义事务 --> <bean id = "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name = "dataSource" ref = "dataSource" /> </bean> <!-- <configuration>--> <!-- <settings>--> <!-- <setting name="mapUnderscoreToCamelCase" value="true"/>--> <!-- </settings>--> <!-- </configuration>--> <!-- 使用注解定义事务 --> <tx:annotation-driven transaction-manager = "transactionManager" /> </beans>
五:创建springMvc配置文件
1.首先配置扫描器,扫描controller层
2.配置拦截器,将创建的拦截类引入,每当访问时,加载此类
3.将映射出的结果赋予格式,这里我们使用的json
4.注册全局异常处理类,如有异常,先用此类拦截然后输出错误结果
5.引入hibernate的vaild框架
<?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: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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--配置扫描器 扫描controller--> <context:component-scan base-package="com.atdp.dpinfo.controller"> </context:component-scan> <aop:aspectj-autoproxy proxy-target-class="true"> </aop:aspectj-autoproxy> <!-- 配置拦截器 --> <mvc:interceptors> <!-- 允许跨域 --> <!-- <mvc:interceptor>--> <!-- <mvc:mapping path="/**"/>--> <!-- <bean class="com.atdp.dpinfo.common.jwt.HttpInterceptor"></bean>--> <!-- </mvc:interceptor>--> <!-- 检验Token --> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.atdp.dpinfo.common.jwt.HeaderTokenInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> <!-- 开启对Spring MVC高级功能的支持 例如: JSR303校验 映射动态请求 ... --> <!--如不此注解 将不会正常返回json--> <mvc:annotation-driven conversion-service="conversionServiceFactoryBean"> <mvc:message-converters> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <!-- MediaTypes --> <property name="supportedMediaTypes"> <list> <value>application/json</value> </list> </property> <!-- FastJsonConfig --> <property name="fastJsonConfig" ref="fastJsonConfig" /> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig"> <!--默认编码格式 --> <property name="charset" value="UTF-8"/> <property name="serializerFeatures"> <list> <value>WriteMapNullValue</value> </list> </property> </bean> <!-- 使用fastJson来支持JSON数据格式 --> <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json</value> </list> </property> <property name="features"> <list> <value>WriteMapNullValue</value> <value>QuoteFieldNames</value> </list> </property> </bean> <!-- 注册全局异常处理器 --> <bean class="com.atdp.dpinfo.common.exception.GloblaException"/> <!--配置自定义类型转换器--> <bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.atdp.dpinfo.common.mvc.StringToDateConverter"/> </set> </property> </bean> <!--hibernate的vail bean级别校验--> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <!--注入hibernate的验证器--> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> </bean> <!--方法级别的校验 要校验的方法所在类必须添加@Validated注解--> <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"> <!-- 可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的参考配置,如果不指定则系统使用默认的 --> <property name="validator" ref="validator" /> </bean> </beans>
六:创建redis属性
redis.host=127.0.0.1 redis.port=6379 #redis.password=123456 #最大空闲数(默认:8) redis.maxIdle=300 #当连接池资源耗尽时,调用者最大阻塞时间,超时将抛出异常.单位:毫秒,默认:-1,表示永不超时. redis.maxWaitMillis=1000 #最大连接数(默认:8) redis.maxTotal=500 #指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 (默认:false) redis.testOnBorrow=true redis.testOnReturn=true redis.testWhileIdle=true redis.blockWhenExhausted=false redis.numTestsPerEvictionRun=1024 redis.timeBetweenEvictionRunsMillis=30000 redis.minEvictableIdleTimeMillis=1800000
七:结合spring-redis,创建文件
1.扫描redis属性文件
2.将连接池设置
3.配置redis的工具类
<?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:p="http://www.springframework.org/schema/p" 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"> <!--扫描redis配置文件--> <!-- <context:property-placeholder ignore-unresolvable="true" location="classpath:redis.properties"/>--> <!--设置连接池--> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大空闲连接数 --> <property name="maxIdle" value="${redis.maxIdle}"/> <!-- 最大连接数 --> <property name="maxTotal" value="${redis.maxTotal}" /> <!-- 每次释放连接的最大数目 --> <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" /> <!-- 释放连接的扫描间隔(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" /> <!-- 连接最小空闲时间 --> <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" /> <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 --> <property name="maxWaitMillis" value="${redis.maxWaitMillis}" /> <!-- 在获取连接的时候检查有效性, 默认false --> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> <property name="testOnReturn" value="${redis.testOnReturn}" /> <!-- 在空闲时检查有效性, 默认false --> <property name="testWhileIdle" value="${redis.testWhileIdle}" /> <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true --> <property name="blockWhenExhausted" value="${redis.blockWhenExhausted}" /> </bean> <!--Spring整合Jedis,设置连接属性--> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:hostName="${redis.host}" p:port="${redis.port}" p:pool-config-ref="poolConfig" p:timeout="100000"/> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="connectionFactory" /> <!-- 如果不配置Serializer,那么存储的时候只能使用String,如果用对象类型存储,那么会提示错误 can't cast to String!!!--> <property name="keySerializer"> <!--对key的默认序列化器。默认值是StringSerializer--> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <!--是对value的默认序列化器,默认值是取自DefaultSerializer的JdkSerializationRedisSerializer。--> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> <!--存储Map时key需要的序列化配置--> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <!--存储Map时value需要的序列化配置--> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> </bean> <!--配置redis工具类bean--> <bean id="redisUtils" class="com.atdp.dpinfo.common.redis.RedisUtil"></bean> </beans>
八:logback文件创建,更改控制台打印内容,设置自己喜欢的颜色
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property resource="config.properties" /> <!-- 控制台输出 --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)</Pattern> </encoder> </appender> <!-- 文件输出 - 全部日志 --> <appender name="file.all" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.output.dir}/all.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${log.output.dir}/archive/%d{yyyy-MM-dd}_all.tar.gz</FileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>%d{yyyy-MM-dd HH:mm:ss} [%-5level] [%thread] %logger{50}#%method:%L %msg%n</Pattern> <charset>UTF-8</charset> </encoder> </appender> <!-- 文件输出 - 访问日志 --> <appender name="file.access" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.output.dir}/access.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${log.output.dir}/archive/%d{yyyy-MM-dd}_access.log.gz</FileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>%msg%n</Pattern> <charset>UTF-8</charset> </encoder> </appender> <!-- 文件输出 - 定时任务执行日志 --> <appender name="file.timedtask" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.output.dir}/timedtask.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${log.output.dir}/archive/%d{yyyy-MM-dd}_timedtask.tar.gz</FileNamePattern> <maxHistory>3</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>%d{yyyy-MM-dd HH:mm:ss} [%-5level] %logger{50}#%method:%L %msg%n</Pattern> <charset>UTF-8</charset> </encoder> </appender> <!--sql日志输出--> <logger name="org.springframework.web.servlet.DispatcherServlet" level="TRACE" additivity="true"/> <logger name="org.apache.ibatis" level="DEBUG" additivity="true"/> <logger name="java.sql.Connection" level="DEBUG" additivity="true"/> <logger name="java.sql.Statement" level="DEBUG" additivity="true"/> <logger name="java.sql.PreparedStatement" level="DEBUG" additivity="true"/> <root level="${log.level.root}"> <appender-ref ref="console" /> <appender-ref ref="file.all" /> <appender-ref ref="file.timedtask" /> </root> </configuration>
九:最为关键的一步,修改web.xml
1.将前几步创建的配置文件加载到此xml中
2.设置编码过滤器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>Archetype Created Web Application</display-name> <!--spring容器加载--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!--日志文件加载--> <context-param> <param-name>logbackConfigLocation</param-name> <param-value>classpath:logback.xml</param-value> </context-param> <!-- 编码过滤器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--spring监听器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- SpringMVC的前端控制器 --> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- SpringMVCp配置文件路径 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <!-- 何时启动,大于0的值表示容器启动时初始化此servlet,正值越小优先级越高 --> <load-on-startup>1</load-on-startup> <!--异步支持--> <!-- <async-supported>true</async-supported>--> </servlet> <!-- SpringMVC拦截设置 --> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
十:配置好框架之后,需要创建返回值包装类
1.创建返回值包装类
public class ResultVo<E> { @JSONField(ordinal = 1) private boolean success; @JSONField(ordinal = 2) private String msg; @JSONField(ordinal = 3) @JsonInclude(JsonInclude.Include.NON_NULL) private E data; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public E getData() { return data; } public void setData(E data) { this.data = data; } @Override public String toString() { return "ResultVo{" + "success=" + success + ", msg='" + msg + '\'' + ", data=" + data + '}'; } }
2.将创建好的返回值包装类利用,创建错误返回方法和成功返回方法
public class ResultVoUtil { public static ResultVo success(String successMsg){ return ResultVoUtil.success(null,successMsg); } public static ResultVo success(Object object,String successMsg){ ResultVo resultVo = new ResultVo(); resultVo.setSuccess(true); resultVo.setMsg(successMsg); resultVo.setData(object); return resultVo; } public static ResultVo error(String errorMsg){ ResultVo resultVo = new ResultVo(); resultVo.setSuccess(false); resultVo.setMsg(errorMsg); return resultVo; } public static ResultVo error(String errorMsg,Object object){ ResultVo resultVo = new ResultVo(); resultVo.setSuccess(false); resultVo.setMsg(errorMsg); resultVo.setData(object); return resultVo; } }
十一:创建全局异常处理
1.创建全局异常处理类
//此注解表示会对controller层的异常进行处理 @RestControllerAdvice public class GloblaException { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //bean级别校验参数时引发的异常 @ExceptionHandler(BindException.class) @ResponseBody public ResultVo MissingRequestParamExceptionHandler(BindException e) { //e.printStackTrace(); ArrayList<String> objects = new ArrayList<>(); List<FieldError> list = e.getFieldErrors(); for (ObjectError objectError : list) { // 输出错误信息 objects.add(objectError.getDefaultMessage()); } logger.error("请求参数校验异常:" + JSON.toJSONString(objects)); return ResultVoUtil.error("请求参数校验异常",objects); } //方法级别校验参数时引发的异常 @ExceptionHandler(ConstraintViolationException.class) @ResponseBody public ResultVo ConstraintViolationException(ConstraintViolationException e) { //e.printStackTrace(); ArrayList<String> objects = new ArrayList<>(); Set<ConstraintViolation<?>> violations = e.getConstraintViolations(); for (ConstraintViolation<?> objectError : violations) { // 输出错误信息 objects.add(objectError.getMessageTemplate()); } logger.error("请求参数校验异常:" + JSON.toJSONString(objects)); return ResultVoUtil.error("请求参数校验异常",objects); } //@ExceptionHandler(Exception.class)表示自定义异常处理方法,参数是指对Exception会进行处理,@ResponseBody表示是返回前端json格式数据 @ExceptionHandler(Exception.class) @ResponseBody public ResultVo toError(Exception e, HttpServletRequest request) { logger.debug(e.getMessage()); logger.error("业务异常:" + request.getRequestURL().toString() + ":" + e.getMessage()); return ResultVoUtil.error("业务异常:" + e.getMessage()); } }
2.创建自定义异常处理类
public class BussinessExcption extends RuntimeException{ public BussinessExcption(String message) { super(message); } public BussinessExcption(String message, Throwable cause) { super(message, cause); } @Override public String getMessage() { return super.getMessage(); } @Override public String toString() { return super.toString(); } }
十二:创建自定义类型转换器
public class StringToDateConverter implements Converter<String, Date> { @Override public Date convert(String source) { if (StringUtils.isBlank(source)) { return null; } long timestamp = Long.parseLong(source); return new Date(timestamp * 1000); } }
十三:创建jwtUtil类
public class JwtUtil { /** * token加密时使用的密钥 * 一旦得到该密钥也就可以伪造token了 */ public static String sercetKey = "InMySchoolOnline"; /** * 代表token的有效时间 * 30天有效时间 */ public final static long keeptime = 30 * 24 * 60 * 60 * 1000; /** * JWT由3个部分组成,分别是 头部Header,载荷Payload一般是用户信息和声明,签证Signature一般是密钥和签名 * 当头部用base64进行编码后一般都会呈现eyJ...形式,而载荷为非强制使用,签证则包含了哈希算法加密后的数据,包括转码后的header,payload和sercetKey * 而payload又包含几个部分,issuer签发者,subject面向用户,iat签发时间,exp过期时间,aud接收方。 * @Title: generToken * @Description: TODO * @param: @param id 用户id * @param: @param issuer 签发者 * @param: @param subject 一般用户名 * @param: @return * @return: String * @throws */ public static String generToken(String id, String issuer, String subject) { long ttlMillis = keeptime; SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //使用Hash256算法进行加密 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //获取系统时间以便设置token有效时间 byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(sercetKey); //将密钥转码为base64形式,再转为字节码 Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //对其使用Hash256进行加密 JwtBuilder builder = Jwts.builder().setId(id).setIssuedAt(now); //JWT生成类,此时设置iat,以及根据传入的id设置token if (subject != null) { builder.setSubject(subject); } if (issuer != null) { builder.setIssuer(issuer); } //由于Payload是非必须加入的,所以这时候要加入检测 builder.signWith(signatureAlgorithm, signingKey); //进行签名,生成Signature if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } //返回最终的token结果 return builder.compact(); } /** * 该函数用于更新token * @Title: updateToken * @Description: TODO * @param: @param token * @param: @return * @return: String * @throws */ public static String updateToken(String token) { //Claims就是包含了我们的Payload信息类 Claims claims = verifyToken(token); String id = claims.getId(); String subject = claims.getSubject(); String issuer = claims.getIssuer(); //生成新的token,根据现在的时间 return generToken(id, issuer, subject); } /** * 将token解密出来,将payload信息包装成Claims类返回 * @Title: verifyToken * @Description: TODO * @param: @param token * @param: @return * @return: Claims * @throws */ public static Claims verifyToken(String token) { Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey)) .parseClaimsJws(token).getBody(); return claims; } }
十四:创建token拦截器
1.获取请求头中token
2.使用jwt令牌解析token
3.校验前端传入token是否正确
4.双重验证:登录时将token信息传入redis缓存中,从缓存中获取到信息后在做校验
public class HeaderTokenInterceptor implements HandlerInterceptor { @Autowired private RedisUtil redisUtil; @Autowired private RedisTemplate redisTemplate; private static final Logger LOG = Logger.getLogger(HeaderTokenInterceptor.class); @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { // 获取我们请求头中的token验证字符 String headerToken = httpServletRequest.getHeader("token"); // 检测当前页面,我们设置当页面不是登录页面时对其进行拦截 // 具体方法就是检测URL中有没有login字符串 if (!httpServletRequest.getRequestURI().contains("login")) { if (headerToken == null) { // 如果token不存在的话,返回错误信息。 throw new BussinessExcption("token为空"); } try { // 对token进行更新与验证 JwtUtil.updateToken(headerToken); LOG.debug("token验证通过"); } catch (Exception e) { LOG.debug("token验证出现异常!"); // 当token验证出现异常返回错误信息,token不匹配 throw new BussinessExcption("token不匹配"); } Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Claims claims = JwtUtil.verifyToken(headerToken); Object user = redisUtil.get(claims.getId()); if (user == null){ throw new BussinessExcption("当前未登录"); } Map map = JSON.parseObject((String) user); String token = String.valueOf(map.get("token")); String tokenTime = String.valueOf(map.get("tokenTime")); if (!token.equals(headerToken)){ throw new BussinessExcption("token不匹配"); } if (sdf.parse(tokenTime).before(date)){ throw new BussinessExcption("token已过期"); } } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }
十五:创建跨域拦截器(此类暂未用到)
public class HttpInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 允许跨域 response.setHeader("Access-Control-Allow-Origin", "*"); // 允许自定义请求头token(允许head跨域) response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
十六:创建redisUtil类
public class RedisUtil { @Autowired private RedisTemplate redisTemplate; //解决redis存放数据乱码问题 @PostConstruct public void init() { initRedisTemplate(); } private void initRedisTemplate() { RedisSerializer stringSerializer = redisTemplate.getStringSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.setHashValueSerializer(stringSerializer); } /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * * @param key 键 * @param delta 要增加几(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key 键 * @param delta 要减少几(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * * @param key 键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time); } return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }