新建Maven工程,打包方式 war:
修改pom.xml 文件增加依赖:
<dependencies>
<!-- 配置spring-webmvc就不用配spring-context了 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- jackson和fastjson,可以任选一个. -->
<!-- <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.5</version>
</dependency> -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
<!-- 根据数据库版本选择connector4j版本. -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<!-- springJDBC和spring主版本相匹配 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- MyBatis主包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<!-- 整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<!-- druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- log4j 作用域:provided -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>provided</scope>
</dependency>
<!-- shiro安全框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 面向切面2个依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!-- 通用Mapper -->
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>mapper</artifactId>
<version>3.0.1</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.2.1</version>
</dependency>
<!-- 发送邮件验证 -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.mail/mail -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
</dependency>
<!-- redis 连接 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 这里是设置不检测web.xml -->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<!-- 这里是设置每次update项目后的JDK版本过低bug -->
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
整体结构图:
配置替代web.xml 的启动类
WebAppInitializer.class
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 关于 AbstractAnnotationConfigDispatcherServletInitializer 在 Servlet 3.0
* 环境下,Servlet 容器会在 classpath 下搜索实现了 javax.servlet.ServletContainerInitializer
* 接口的任何类,找到之后用它来初始化 Servlet 容器。
*
* Spring 实现了以上接口,实现类叫做 SpringServletContainerInitializer, 它会依次搜寻实现了
* WebApplicationInitializer的任何类,并委派这个类实现配置。 之后,Spring 3.2 开始引入一个简易的
* WebApplicationInitializer 实现类, 这就是
* AbstractAnnotationConfigDispatcherServletInitializer。 所以
* SpittrWebAppInitializer 继承
* AbstractAnnotationConfigDispatcherServletInitializer之后, 也就是间接实现了
* WebApplicationInitializer,在 Servlet 3.0 容器中,它会被自动搜索到,被用来配置 servlet 上下文。
*
*/
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 注册顺序->listener->filter(Shiro安全框架)->servlet(SpringMVC)..
* 必须按照顺序注册,所以必须重写onStarup
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// super.onStartup(servletContext);
registerContextLoaderListener(servletContext);
// 在这里注册shiro过滤器
registerSiroFilter(servletContext);
registerDispatcherServlet(servletContext);
}
/**
* 注册过滤器解决post乱码问题
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter=new CharacterEncodingFilter();
filter.setEncoding("utf-8");
filter.setForceEncoding(true);
return new Filter[] {filter};
}
/**
* 注册shiro配置
* @param servletContext
*/
private void registerSiroFilter(ServletContext servletContext) {
// 注册Filter对象
// 什么时候需要采用此方式进行注册?
// 项目没有web.xml并且此filter不是自己写的
FilterRegistration.Dynamic dy = servletContext.addFilter("filterProxy", DelegatingFilterProxy.class);
dy.setInitParameter("targetBeanName", "shiroFilterFactoryBean");
dy.addMappingForUrlPatterns(null, // EnumSet<DispatcherType>//不写就是默认所有的.
false, // 是否精确匹配
"/*");// url-pattern
}
/**
* 此方法负责加载Service和其他第三方包的初始化配置如service层和DataAccessObject层的框架和类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { AppRootConfig.class,AppShiroConfig.class,AppRedisConfig.class };
}
/**
* 这里写入用以取代Spring和SpringMVC的配置文件
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { AppServletConfig.class };
}
/**
* 这里配置默认前端控制器.这里所有匹配*.do的访问都会被捕捉到. 也可以配置为"/" 不会拦截.jsp /* 全部拦截 不能使用.log 结尾,否则阿里巴巴 fastjson转换报错
*/
@Override
protected String[] getServletMappings() {
return new String[] {"*.xiami"};
}
}
持久层Mybatis配置类:
/** 配置JDBC,MyBatis相关配置 每个配置类专注于自己的内容*/
@Configuration
@ComponentScan(value = "com.xiami", excludeFilters = { // 这里需要把MVC相关注解排除,否则test会报错!~无法创建bean工厂
@Filter(classes = { Controller.class, ControllerAdvice.class, Configuration.class }) })
@PropertySource("classpath:property/jdbc.properties") // 读取JDBC配置文件
@MapperScan("com.xiami.**.dao")//包扫描 接口
@EnableAspectJAutoProxy // 启动切面
//@EnableTransactionManagement//启动事务
public class AppRootConfig {
/**
* 让系统支持多个properties文件应用,否则shiro会出错
*
* @return
*/
@Bean
public PropertySourcesPlaceholderConfigurer newPropertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
/**
* druid相关配置
* @param driverClass
* @param jdbcUrl
* @param username
* @param password
* @return
*/
@Bean(value = "dataSource", initMethod = "init", destroyMethod = "close") // <bean id="dataSource"
@Lazy(false)
public DataSource newDataSource(@Value("${jdbcDriver}") String driverClass, @Value("${jdbcUrl}") String jdbcUrl,
@Value("${jdbcUser}") String username, @Value("${jdbcPassword}") String password) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(jdbcUrl);
dataSource.setDriverClassName(driverClass);
dataSource.setUsername(username);
dataSource.setPassword(password);
// 配置其他东西自行使用set方法设置
return dataSource;
}
/**
* 整合MyBatis配置文件,接管SqlSessionFactory
* @param dataSource
* @return
* @throws IOException
*/
@Bean
@Lazy(false)
public SqlSessionFactoryBean newSqlSessionFactory(@Autowired /* 根据属性类型自动装配,可以省略 */ DataSource dataSource)
throws IOException {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
Resource[] mapperLocations = new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*Mapper.xml");
bean.setMapperLocations(mapperLocations);
// 设置启动驼峰自动映射
org.apache.ibatis.session.Configuration conn = new org.apache.ibatis.session.Configuration();
conn.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(conn);
//配置myBatis,pageHelper插件
PageHelper pHelper=new PageHelper();
Properties p=new Properties();
p.setProperty("resonable", "true");
p.setProperty("dialect", "mysql");
p.setProperty("rowBoundsWithCount", "true");
pHelper.setProperties(p);
//配置通用mapper
MapperInterceptor interceptor=new MapperInterceptor();
Properties mapperProperties=new Properties();
mapperProperties.setProperty("IDENTITY","MYSQL");
mapperProperties.setProperty("mappers", "com.xiami.common.mapper.CommonMapper");
interceptor.setProperties(mapperProperties);
Interceptor[] mapper = {pHelper,interceptor};
bean.setPlugins(mapper);
return bean;
}
@Bean("txManager") // 需要在最顶端用注解@Enable来启用事物管理器
public DataSourceTransactionManager newData(DataSource dSource) {
DataSourceTransactionManager dstm = new DataSourceTransactionManager(dSource);
return dstm;
}
/*切面类*/
@Bean//将aop类加载到容器
public AopService getAopService(){
AopService aopService= new AopService();
return aopService;
}
/*
* public MapperScannerConfigurer newMapperScannerConfigurer() {
* MapperScannerConfigurer msc=new MapperScannerConfigurer();
* msc.setBasePackage("com.jt.**.dao");
* msc.setSqlSessionFactoryBeanName("newSqlSessionFactory"); return msc; }
*/// 用上面那个@MapperScan("com.jt.**.dao")代替
}
配置SpringMVC的配置类:
//通常让每个的配置文件只干自己的事,就设置过滤器
@ComponentScan(value="com.xiami",includeFilters={@Filter(classes= {Controller.class,ControllerAdvice.class})},useDefaultFilters=false)//这里配置包扫描路径
@EnableWebMvc//启用MVC默认配置
@Configuration//有包扫描则可以省略
public class AppServletConfig extends WebMvcConfigurerAdapter{
/**
* 配置视图解析器
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/pages/",".html");
}
/**
* 加入基于注解方式整合fastjson
* 可以参考添加如下配置.
*/
//整合fastjson库
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//1.构建MessageConverter对象
FastJsonHttpMessageConverter msConverter = new FastJsonHttpMessageConverter();
//2.配置MessageConverter对象
//2.1设置fastjson基本配置
FastJsonConfig config = new FastJsonConfig();
config.setSerializeConfig(SerializeConfig.globalInstance);
//禁用循环引用问题
config.setSerializerFeatures(
SerializerFeature.DisableCircularReferenceDetect);
msConverter.setFastJsonConfig(config);
//2.2 设置MessageConverter对象对媒体的支持
List<MediaType> list = new ArrayList<>();
list.add(new MediaType("text", "html", Charset.forName("utf-8")));
list.add(new MediaType("application", "json", Charset.forName("utf-8")));
msConverter.setSupportedMediaTypes(list);
//3.将MessageConverter对象添加到converters容器
converters.add(msConverter);
}
/**
* 拦截器
*/
}
配置redis 集群配置类:
/**
* redis 集群的配置文件
* @author Donald 下午6:56:47
*/
@Configuration
@PropertySource("classpath:property/redis.properties") // 读取redisCluter配置文件
public class AppRedisConfig {
/**
* redis连接池
* @param maxIdle 最大空闲数 int 毫秒
* @param maxWait 最大建立连接等待时间 int 毫秒
* @param testOnBorrow 是否取出前在连接池中校验 boolean
* @param maxTotal 最大连接数 int 毫秒
* @param minIdle 最小空闲数 int 毫秒
* @return
*/
@Bean("jedisPoolConfig")
public JedisPoolConfig newJedisPoolConfig(@Value("${redis.maxIdle}") int maxIdle,
@Value("${redis.maxWait}") long maxWait
,@Value("${redis.testOnBorrow}") boolean testOnBorrow
,@Value("${redis.maxTotal}") int maxTotal
,@Value("${redis.minIdle}") int minIdle){
JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setTestOnBorrow(testOnBorrow);
return jedisPoolConfig;
}
/**
* redis 集群工厂
* @param jedisPoolConfig
* @return
*/
@Bean
public JedisClusterFactory newJedisClusterFactory( @Autowired JedisPoolConfig jedisPoolConfig){
JedisClusterFactory jedisClusterFactory=new JedisClusterFactory();
jedisClusterFactory.setPoolConfig(jedisPoolConfig);
jedisClusterFactory.setRedisNodePrefix("redis.cluster");
jedisClusterFactory.setPropertySource(new ClassPathResource("property/redis.properties"));//路径
return jedisClusterFactory;
}
}
Shiro 安全框架配置类:
/**定义shiro框架的配置信息*/
@Configuration
public class AppShiroConfig {
public AppShiroConfig() {
System.out.println("启动shiro");//打印
}
/**
* 核心配置1 配置shiro的SecurityManager
*/
@Bean("securityManager")
public SecurityManager newSecurityManager(AuthorizingRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 通过realm访问数据库
securityManager.setRealm(realm);
return securityManager;
}
/**
* 核心配置2 shiroFilterFactory工厂
*/
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean newShiroFilterFactoryBean(SecurityManager securityManager) {// shiro 包
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 当此用户是一个非认证用户,需要先登陆进行认证
bean.setLoginUrl("/login.login");
LinkedHashMap<String, String> fcMap = new LinkedHashMap<>();
// fcMap.put("/pages/***", "authc");// anon表示允许匿名访问
// fcMap.put("/login", "anon");
fcMap.put("/**", "anon");// 必须授权/认证才能访问
bean.setFilterChainDefinitionMap(fcMap);
return bean;
}
/**
* 下面3个方法用于生命周期管理,注意,如若需要在Controller中使用权限控制,需要将以下3个方法丢到AppServletConfig中,否则只会在Service中生效
* @return
*/
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor newLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**配置负责为Bean对象(需要授权访问的方法所在的对象)
* 创建代理对象的Bean组件*/
@DependsOn(value="lifecycleBeanPostProcessor")
@Bean
public DefaultAdvisorAutoProxyCreator newDefaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
/**
* 配置授权属性应用对象(在执行授权操作时需要用到此对象)
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor newAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor bean = new AuthorizationAttributeSourceAdvisor();
bean.setSecurityManager(securityManager);
return bean;
}
}
数据库链接信息jdbc.properties:
jdbcDriver=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/xiamimusic_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
jdbcUser=root
jdbcPassword=root
Redis 集群配置信息:
#最小空闲数
redis.minIdle=100
#最大空闲数
redis.maxIdle=300
#最大连接数
redis.maxTotal=1000
#客户端超时时间单位是毫秒
redis.timeout=5000
#最大建立连接等待时间
redis.maxWait=1000
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
#redis cluster
redis.cluster0=176.53.5.94:7000
redis.cluster1=176.53.5.94:7001
redis.cluster2=176.53.5.94:7002
redis.cluster3=176.53.5.94:7003
redis.cluster4=176.53.5.94:7004
redis.cluster5=176.53.5.94:7005
redis.cluster6=176.53.5.94:7006
redis.cluster7=176.53.5.94:7007
redis.cluster8=176.53.5.94:7008
Log4j.properties:
log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n
log4j.logger.com.mybatis3=DEBUG
log4j.logger.com.jt=DEBUG
UserMapper.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.xiami.login.dao.UserDao">
<!-- 根据用户查询用户信息 -->
<select id="selectByUsername" resultType="com.xiami.login.entity.UserEntity" parameterType="String">
select * from user where username=#{username}
</select>
<!-- 注册用户 -->
<insert id="insertUser" parameterType="com.xiami.login.entity.UserEntity" >
insert into user (username,password,created_time,modified_time,password_md5,password_salt,email,salt)
values(#{username},#{password},#{createdTime},#{modifiedTime},#{passwordMd5},#{passwordSalt},#{email},#{salt})
</insert>
<!-- 根据用户名获取用户的权限 -->
<select id="selectPermissionByUsername" resultType="String" parameterType="String">
SELECT salt from user where username=#{username}
</select>
<!-- 查询用户名的可用性 -->
<select id="queryUsername" resultType="int" parameterType="String">
select count(*) from user where username=#{username}
</select>
<!-- 查询邮箱的可用性 -->
<select id="queryEmail" resultType="int" parameterType="String">
select count(*) from user where email=#{email}
</select>
<!-- 根据用户名 更新用户的最后登录时间 -->
<update id="updateUserLastLogDate">
update user set last_login=#{lastLogin} where username=#{username}
</update>
</mapper>
AOP切面类:
@Aspect//标记此类为aop 切面类
public class AopService {
@Autowired
private UserDao userDao;
/* com.xiami.login.serviceImpl.UserLoginServiceImpl.saveUserInformation(String, HttpServletRequest, HttpServletResponse)*/
/**
* 定义公共切入点
* 修饰符 返回值类型 方法名全名 参数列表
* public int com.fei.user.*(..) >>>表示user类下的所有方法
*
*/
@Pointcut("execution(public void com.xiami.login.serviceImpl.UserLoginServiceImpl.saveUserInformation(..))")
public void pointCut(){}
@Before("pointCut()")//目标方法执行之前
public void loginBefore(){
System.out.println("登录成功,正在往Redis集群写数据>>>befoere");
}
@After(value="pointCut()")//目标方法执行之后
public void loginEnd(){
System.out.println("向redis集群写入数据结束>>>after");
}
@AfterReturning(value="pointCut()",returning="result")//目标方法正常返回之后执行,result获取结果,有joinpoint 必须放在第一位
public void LonginReturn(JoinPoint joinPoint,Object result){
String methodName=joinPoint.getSignature().getName();//获取被切的方法名字
Object[] args = joinPoint.getArgs();
String username=(String)args[0];//用户名
userDao.updateUserLastLogDate(username,new Date());//更新用户最后的登录时间
System.out.println("向Redis集群写如数据正常>>>目标正常完成"+result);
}
@AfterThrowing(value="pointCut()",throwing="expect")//目标方法执行异常 expect 异常信息
public void loginExpection(Exception expect){
System.out.println("目标方法志执行抛出异常 咯>>>expection"+expect);
}
}
类2:
@Aspect//这是一个切面类
public class LoginFailLimit {
//@Pointcut("ececution( * com.xiami.login.userRealm.UserRealm.doGetAuthenticationInfo(AuthenticationToken))")
@Pointcut("execution(* com.xiami.login.controller.UserLoginController.login(..))")
public void pintCut(){}
@Before("pintCut()")
public void logStart(){
System.out.println("kkk>>>切全");
}
@AfterReturning("pintCut()")//正常返回
public void logOK(){
System.out.println("登录OK");
}
@AfterThrowing("pintCut()")//登录异常
public void logFail(){
System.out.println(">>>>登录异常");
}
}
权限认证类:
/** 本类用与用户登录验证,及权限的分配*/
@Service
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
/* 设置凭证(Credentials)匹配器
*/
@Override
public void setCredentialsMatcher(
CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher cMatcher=
new HashedCredentialsMatcher();
cMatcher.setHashAlgorithmName("MD5");
//设置加密的次数(这个次数应该与保存密码时那个加密次数一致)
cMatcher.setHashIterations(2);
super.setCredentialsMatcher(cMatcher);
}
@Override
public String getName() {
return "userRealm";//return super.getName();
}
@Override//用于权限验证,返回用户的权限信息
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
//1.获取用户名
String username = principals.getPrimaryPrincipal().toString();
//2.开始授权操作
ArrayList<String> userPermission = userDao.selectPermissionByUsername(username);
//3.将用户的权限信息封装到AuthorizationInfo 中返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (String string : userPermission) {
info.addStringPermission(string);//增加权限到对应的用户中
}
return info;
}
@Override//用于登录验证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户名
String username=(String)token.getPrincipal();
//2.查询数据库获取用户的信息、
UserEntity userEntity=userDao.selectByUsername(username);
if(userEntity==null)
throw new ServiceException("用户名或者密码错误");
//3.从数据库中获取密码
//根据规则 加密密码结果 数据出存储的原始密码是1111 盐是xiami 2次散列
String password= userEntity.getPasswordMd5();//加密后的密码
String salt=userEntity.getPasswordSalt();//盐值
//4.对盐值进行格式化
ByteSource bytes = ByteSource.Util.bytes(salt);
//5.将从数据库中数据封装到SimpleAuthenticationInfo中返回
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(
username, //用户名
password,//加密后的密码
bytes,//盐值
getName());//realmeName(userRealm)
return info;
}
}
Redis集群工场类:
//通过工厂模式创建JedisCluster对象
public class JedisClusterFactory implements FactoryBean<JedisCluster>{
private Resource propertySource; //表示注入properties文件
private JedisPoolConfig poolConfig; //注入池对象
private String redisNodePrefix; //定义redis节点的前缀
@Override
public JedisCluster getObject() throws Exception {
Set<HostAndPort> nodes = getNodes(); //获取节点信息
JedisCluster jedisCluster =
new JedisCluster(nodes, poolConfig);
return jedisCluster;
}
//获取redis节点Set集合
public Set<HostAndPort> getNodes(){
//1.准备Set集合
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
//2.创建property对象
Properties properties = new Properties();
try {
properties.load(propertySource.getInputStream());
//2.从配置文件中遍历redis节点数据
for (Object key : properties.keySet()) {
String keyStr = (String) key;
//获取redis节点数据
if(keyStr.startsWith(redisNodePrefix)){
//IP:端口
String value = properties.getProperty(keyStr);
String[] args = value.split(":");
HostAndPort hostAndPort = new HostAndPort(args[0],Integer.parseInt(args[1]));
nodes.add(hostAndPort);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return nodes;
}
@Override
public Class<?> getObjectType() {
return JedisCluster.class;
}
@Override
public boolean isSingleton() {
return false;
}
public Resource getPropertySource() {
return propertySource;
}
public void setPropertySource(Resource propertySource) {
this.propertySource = propertySource;
}
public JedisPoolConfig getPoolConfig() {
return poolConfig;
}
public void setPoolConfig(JedisPoolConfig poolConfig) {
this.poolConfig = poolConfig;
}
public String getRedisNodePrefix() {
return redisNodePrefix;
}
public void setRedisNodePrefix(String redisNodePrefix) {
this.redisNodePrefix = redisNodePrefix;
}
}
发送邮箱验证码工具类:
/** 这是一个发送邮箱验证码的工具类*/
public class EmailSendUntils {
private static Properties properties;
static{
properties = new Properties();
properties.put("mail.transport.protocol", "smtp");// 连接协议
properties.put("mail.smtp.host", "smtp.qq.com");// 主机名
properties.put("mail.smtp.port", 465);// 端口号
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.ssl.enable", "true");//设置是否使用ssl安全连接 ---一般都使用
properties.put("mail.debug", "true");//设置是否显示debug信息 true 会在控制台显示相关信息
}
/**
* 发送成功返回sendOK 失败sendfail
* @param emailAddress 收件人邮箱地址
* @return [0] OK/Fail 邮件是否发送成功 [1] 验证码
* @throws Exception
* @throws AddressException
*/
public static String[] sendEmail(String emailAddress) {
String[] resulte ={"Fail",""};//邮件发送结果 SendOK 邮件发送成功
//得到回话对象
Session session = Session.getInstance(properties);
// 获取邮件对象
Message message = new MimeMessage(session);
try {
//设置发件人邮箱地址
message.setFrom(new InternetAddress("942014005@qq.com"));
//设置收件人地址
message.setRecipients(RecipientType.TO,new InternetAddress[] { new InternetAddress(emailAddress) });
//设置邮件标题
message.setSubject("这是第一封虾米音乐网站注册验证邮件");
//获取随机验证码!!!!!
String itemID = getItemID();
//设置邮件内容
message.setText("你的虾米音乐网验证码是:"+itemID+"\r\n"+"验证码将在10分钟后失效");
//得到邮差对象
Transport transport = session.getTransport();
//连接自己的邮箱账户
transport.connect("9420140005@qq.com", "omkgfxkfyzzpbcda");
transport.sendMessage(message, message.getAllRecipients()); //发送邮件
resulte[0]="OK"; //邮件发送成功
resulte[1]=itemID;//密码为刚才得到的授权码
} catch (MessagingException e) {
e.printStackTrace();
}
return resulte;
}
/**
* 产生六位数的随机邮箱验证码
* @return
*/
private static String getItemID(){
int n =6;
String val = "";
Random random = new Random();
for ( int i = 0; i < n; i++ )
{
String str = random.nextInt( 2 ) % 2 == 0 ? "num" : "char";
if ( "char".equalsIgnoreCase( str ) )
{ // 产生字母
int nextInt = random.nextInt( 2 ) % 2 == 0 ? 65 : 97;
val += (char) ( nextInt + random.nextInt( 26 ) );
}
else
{ // 产生数字
val += String.valueOf( random.nextInt( 10 ) );
}
}
return val;
}
}
配置全局异常类:
@ControllerAdvice
public class UserLoginExpection {
@ExceptionHandler(ShiroException.class)
@ResponseBody
public JsonResult LoginException(Exception e) {
e.printStackTrace();
return new JsonResult(201, "用户名或者密码错误!!!");
}
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JsonResult serviceException(Exception e){
e.printStackTrace();
return new JsonResult(201,"服务器异常请联系超哥");
}
}
##配置自定义异常类,更方便定义异常的位置:
/**
* 自定义异常编写的目的就是为了
* 更加准确的定位你的业务以及错误.
*/
public class ServiceException
extends RuntimeException {
private static final long serialVersionUID = 7793296502722655579L;
public ServiceException() {
super();
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
public ServiceException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public ServiceException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
基础控制层类Controller:
/** 登录注册控制*/
@Controller
@RequestMapping("/")
public class UserLoginController {
private static final Md5Hash String = null;
@Autowired
private UserLoginService userLoginService;
@Autowired
public JedisCluster jedisCluster;
@RequestMapping("doLogin")//访问登录界面
public String doLoginUI() {
return "login";
}
@RequestMapping("index")//主页面
public String indexPages(){
return "menuMusic";
}
@RequestMapping("user/login")//登录验证
@ResponseBody
public JsonResult login(String username,String password,HttpServletRequest request,HttpServletResponse response) {
//1.对用户身份以及凭证信息进行封装
UsernamePasswordToken token=
new UsernamePasswordToken(username,password);
//2. 获取Shiro框架中的主体对象
Subject subject=SecurityUtils.getSubject();
//3. 通过主体对象提交用户token信息
subject.login(token);
//4.用户登录成功存储用户信息到Redis 集群
userLoginService.saveUserInformation(username,request,response);
return new JsonResult(200,"OK");
}
@RequestMapping("user/logout")//用户登出
public String logout(HttpServletRequest request,HttpServletResponse response) {
//1.清掉Redis缓存
userLoginService.cleanRedisUser(request,response);
//1. 获取Shiro框架中的主体对象
Subject subject=SecurityUtils.getSubject();
//2.登出
subject.logout();
return "login";
}
@RequestMapping("user/regist")//用户注册
@ResponseBody
public JsonResult registUser(String username,String password,String email,String emailCode) {
UserEntity userEntity=new UserEntity();
JsonResult jsonResult = userLoginService.checkEmaileCode(email,emailCode);//先判断邮箱与验证码
if(jsonResult.state==200){
userEntity.setPassword(password);
userEntity.setUsername(username);
userEntity.setEmail(email);
int result = userLoginService.insertUser(userEntity);
if(result==0)
return new JsonResult(201,"用户注册失败");
return new JsonResult(200,"注册测功");
}else{
return jsonResult;
}
}
/**
* 查询邮箱是否被注册
* @param email 邮箱地址
* @param type 数据类型 2 邮箱验证 查询类型 1 用户名 验证
* @return
*/
@RequestMapping("user/query")
@ResponseBody
public JsonResult queryEmail(String type,String data){
System.out.println(data+""+type);
if("1".equals(type)){
JsonResult sJsonResult= userLoginService.queryUsername(data);//检查用户名
return sJsonResult;
}else if("2".equals(type)){
return userLoginService.queryEmail(data);//检查邮箱地址
}else{
return new JsonResult(201,"指令错误");
}
}
/**
* 发送邮箱验证码
* @param email
* @return
*/
@RequestMapping("user/sendEmail")
@ResponseBody
public JsonResult sendEmail(String email){
if(!StringUtils.isEmpty(email)){
return userLoginService.sendEmail(email);
}else{
return new JsonResult(201,"邮箱数据出错");
}
}
/**
* 检查邮箱验证码
* @param email 邮箱
* @param emailCode 验证码
* @return
*/
@RequestMapping("user/emailCode")
@ResponseBody
public JsonResult checkEmailCode(String email,String emailCode){
return userLoginService.checkEmaileCode(email,emailCode);
}
/**
* 查询用户是否已经登录
* @param request
* @param response
* @return
*/
@RequestMapping("user/checkLogin")
@ResponseBody
public JsonResult checkLogin(HttpServletRequest request,HttpServletResponse response){
JsonResult jsonResult = userLoginService.checkLogin(request,response);
System.out.println(jsonResult);
return jsonResult;
}
}
业务层接口:
public interface UserLoginService {
/**
* 查询用户名是否已经注册,以及用户登录验证
* @param username
* @return
*/
UserEntity selectByUsername(String username);
/**
* 注册用户信息
* @param userLogin
* @return
*/
int insertUser(UserEntity userEntity);
/**
* 通过用户名获取用户的权限信息
* @return
*/
ArrayList<String> selectPermissionByUsername(String username);
/**
* 发送邮件
* @param email
*/
JsonResult sendEmail(String email);
//验证码验证
JsonResult checkEmaileCode(String email, String emailCode);
//用户名验证
JsonResult queryUsername(String username);
//邮箱验证
JsonResult queryEmail(String email);
/** 将用户信息存到 Redis 缓存*/
void saveUserInformation(java.lang.String username, HttpServletRequest request, HttpServletResponse response);
/** 将用户信息从Redis 缓存清掉*/
void cleanRedisUser(HttpServletRequest request, HttpServletResponse response);
/** 查询用户信息是否存在Redis中并返回*/
JsonResult checkLogin(HttpServletRequest request, HttpServletResponse response);
}
业务层实现类:
@Service
public class UserLoginServiceImpl implements UserLoginService {
@Autowired
private UserDao userDao;
@Autowired
public JedisCluster jedisCluster;
private static ObjectMapper objectMapper = new ObjectMapper();//fastjson 用于转换用户成字符串
@Override//通过用户名查询,shiro登录验证,及用户注册时用户名重复验证
public UserEntity selectByUsername(String username) {
return userDao.selectByUsername(username);
}
@Override//用于用户注册
public int insertUser(UserEntity userEntity) {
userEntity.setCreatedTime(new Date());
userEntity.setModifiedTime(new Date());
userEntity.setSalt("0");
String password=userEntity.getPassword();
//对密码进行md5加密
String passwordSalt= String.valueOf(System.currentTimeMillis());
userEntity.setPasswordSalt(passwordSalt);//设置盐值
Md5Hash md5Hash=new Md5Hash(password,passwordSalt,2);//对密码进行盐值 xiami 循环加密两次
userEntity.setPasswordMd5(md5Hash.toString());
System.out.println(userEntity);
int result = userDao.insertUser(userEntity);//注册用户
return result;
}
@Override//更据用户名查询用户的权限信息
public ArrayList<String> selectPermissionByUsername(String username) {
// TODO Auto-generated method stub
return null;
}
@Override/*发送邮件消息*/
public JsonResult sendEmail(String email) {
Md5Hash md5Hash= new Md5Hash(email);
String emailMd5=md5Hash.toString();
if(jedisCluster.get(emailMd5)==null){
String[] emailcode = EmailSendUntils.sendEmail(email);
System.out.println(emailcode[1]+"本次邮箱的验证码");
if("OK".equals(emailcode[0])){//判断是否发送成功
jedisCluster.set(emailMd5, emailcode[1]);//取出验证码
jedisCluster.expire(emailMd5, 60*10);//设置存活时间
return new JsonResult(200,"邮件发送成功");
}else{
return new JsonResult(201,"邮件发送失败请检查邮箱格式");
}
}else{
return new JsonResult(200,"邮件还未过期");
}
}
@Override//邮箱验证码操作
public JsonResult checkEmaileCode(String email, String emailCode) {
Md5Hash md5Hash=new Md5Hash(email);
String token = md5Hash.toString();
String Code=jedisCluster.get(token);
if(Code!=null){
if(Code.equals(emailCode)){
return new JsonResult(200,"OK");
}else{
return new JsonResult(201,"验证码错误");
}
}else{
return new JsonResult(201,"验证码已经过期");
}
}
@Override//查询用户名的可用性
public JsonResult queryUsername(String username) {
int i =userDao.queryUsername(username);
return i>0?new JsonResult(200,"用户名已注册"):new JsonResult(200,"OK");
}
@Override//查询邮箱的可用性
public JsonResult queryEmail(String email) {
int i =userDao.queryEmail(email);
return i>0?new JsonResult(201,"邮箱已注册"):new JsonResult(200,"OK");
}
@Override//存储用户数据
public void saveUserInformation(String username, HttpServletRequest request, HttpServletResponse response) {
Md5Hash md5Hash=new Md5Hash(username+""+System.currentTimeMillis());
String token=md5Hash.toString();
Cookie cookie=new Cookie("XIAMI_LOGIN", token);
cookie.setMaxAge(10*60);//单位秒
cookie.setPath("/");//设置cookie存储路径
response.addCookie(cookie);
UserEntity userEntity = userDao.selectByUsername(username);
String userJSON=null;
try {
userJSON=objectMapper.writeValueAsString(userEntity);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
jedisCluster.set(token, userJSON);
jedisCluster.expire(token, 10*60);//设置键值的存活时间
}
/**
* 清除用户redis 和浏览器的cookie信息
*/
@Override
public void cleanRedisUser(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for (Cookie cookie : cookies) {
if("XIAMI_LOGIN".equals(cookie.getName())){
Cookie cookie1=new Cookie("XIAMI_LOGIN", cookie.getValue());
cookie.setMaxAge(0);//单位秒
cookie.setPath("/");//设置cookie存储路径
response.addCookie(cookie);
jedisCluster.del(cookie.getValue());//删除Redis 记录
return;
}
}
}
}
/** 查询用户是否已经登录*/
@Override
public JsonResult checkLogin(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for (Cookie cookie : cookies) {
if("XIAMI_LOGIN".equals(cookie.getName())){
String userJSON=jedisCluster.get(cookie.getValue());
if(userJSON==null){
return new JsonResult(201,"FAIL");
}
UserEntity userEntity=null;
try {
userEntity = objectMapper.readValue(userJSON, UserEntity.class);
userEntity.setAddress(cookie.getValue());
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(201,"FAIL");
}
JsonResult jsonResult=new JsonResult(userEntity,"OK");
jsonResult.setState(200);
return jsonResult;
}
}
}
return new JsonResult(201,"FAIL");
}
}
持久层接口:
public interface UserDao {
/**
* 查询用户名是否已经注册,以及用户登录验证
* @param username
* @return
*/
UserEntity selectByUsername(String username);
/**
* 注册用户信息
* @param userLogin
* @return
*/
int insertUser(UserEntity userLogin);
/**
* 通过用户名获取用户的权限信息
* @return
*/
ArrayList<String> selectPermissionByUsername(String username);
/**
* 查询用户名的可用性
* @param username
* @return
*/
int queryUsername(String username);
/**
* 邮箱名的可用性
* @param username
* @return
*/
int queryEmail(String email);
/**
* 更具用户名更新用户最后一次登录的时间 使用aop 切面方式
*/
void updateUserLastLogDate(@Param("username") String username,@Param("lastLogin")Date last_login);
}
httpClient类:
http请求:
@Service
public class HttpClientServer {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientServer.class);
@Autowired(required=false)
private CloseableHttpClient httpClient;
@Autowired(required=false)
private RequestConfig requestConfig;
/**
* 1.实现httpclientpost方法
* 1.1设定url 参数
* 1.2设定参数post
* map<string,String> 使用map数据结构实现参数封装
* 1.3 设定字符集编码utf-8
*
* post 请求如何传递参数
* @throws UnsupportedEncodingException
*/
public String doPost(String url,Map<String, String>params,String charset){
String resulte=null;
try {
//1.判断字符集编码是否为空
if(StringUtils.isEmpty(charset))
charset="utf-8";
//2获取请求对象的实体
HttpPost httpPost=new HttpPost(url);
httpPost.setConfig(requestConfig);
//3.判断用户是否传递了参数
if(params!=null) {
//将用户传入的数据map 封装到list集合中
List<NameValuePair> parameters = new ArrayList<>();
for (Map.Entry<String, String> entity : params.entrySet()) {
BasicNameValuePair basicNameValuePair=new BasicNameValuePair(entity.getKey(), entity.getValue());
parameters.add(basicNameValuePair);
}
//实现参数封装
UrlEncodedFormEntity entity=new UrlEncodedFormEntity(parameters,charset);
//将参数封装为Fromentity进行数据传输
httpPost.setEntity(entity);
}
//4.发起post请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
//5.判断请求是否正确
if(httpResponse.getStatusLine().getStatusCode()==200) {
//6.获取服务端回传的数据
resulte = EntityUtils.toString(httpResponse.getEntity(),charset);
}
} catch (Exception e) {
e.printStackTrace();
}
return resulte;
}
public String doPost(String url) {
return doPost(url, null, url);
}
public String doPost(String url,Map<String, String>params) {
return doPost(url, params, null);
}
/**
* 实现get 请求
* 说明:
* get请求参数经过拼接形成
*
*/
public String doGet(String url,Map<String, String>params,String charset){
String resulte=null;
try {
//1.判断字符集编码是否为空
if(StringUtils.isEmpty(charset))
charset="utf-8";
//2.判断用户是否传递了参数
if(params!=null) {
//拼接参数 通过工具类 自动拼接
URIBuilder builder = new URIBuilder(url);
for (Map.Entry<String, String> entity : params.entrySet()) {
builder.addParameter(entity.getKey(), entity.getValue());
}
//将路径拼接 addu?uu=pp&kk=iii
url=builder.build().toString();
}
//3.定义请求类型
HttpGet httpGet =new HttpGet(url);
httpGet.setConfig(requestConfig);
//4.发起get请求
CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
//5.判断请求是否正确
if(httpResponse.getStatusLine().getStatusCode()==200) {
//6.获取服务端回传的数据
resulte = EntityUtils.toString(httpResponse.getEntity(),charset);
}
} catch (Exception e) {
e.printStackTrace();
}
return resulte;
}
public String doGet(String url) {
return doGet(url, null, null);
}
public String doGet(String url,Map<String, String>params) {
return doGet(url, params, null);
}
}