全注解配置SSM

新建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("[email protected]"));       
			//设置收件人地址        
			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("[email protected]", "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);
	    }

}

其他问题以后学习中改正》。。。。。

猜你喜欢

转载自blog.csdn.net/qq_16183731/article/details/83898024