技术学院

07-11
2017
基于Maven+SSM整合shiro+Redis实现后台管理项目
本项目由卖咸鱼叔叔开发完成,欢迎大神指点,慎重抄袭!参考了sojson提供的demo,和官方文档介绍。完整实现了用户、角色、权限CRUD及分页,还有shiro的登录认证+授权访问控制。项目架构:Maven + SpringMVC + Spring + Mybatis + Shiro + Redis数据库:MySql前端框架:H-ui 首先创建Maven项目1.pom.xml 加入依赖包<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>sys</groupId>  <artifactId>sys</artifactId>  <packaging>war</packaging>  <version>0.0.1-SNAPSHOT</version>  <name>sys Maven Webapp</name>  <url>http://maven.apache.org</url>        <dependencies>            <!-- spring依赖管理 -->            <!-- spring-context 是使用spring的最基本的环境支持依赖,会传递依赖core、beans、expression、aop等基本组件,以及commons-logging、aopalliance -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-context</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                        <!-- spring-context-support 提供了对其他第三方库的内置支持,如quartz等 -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-context-support</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                        <!-- spring-orm 是spring处理对象关系映射的组件,传递依赖了jdbc、tx等数据库操作有关的组件 -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-orm</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                        <!-- spring-webmvc 是spring处理前端mvc表现层的组件,也即是springMVC,传递依赖了web等web操作有关的组件 -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-webmvc</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                        <!-- spring-aspects 增加了spring对面向切面编程的支持,传递依赖了aspectjweaver -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-aspects</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                                    <!-- json解析, springMVC 需要用到 -->            <dependency>                <groupId>com.fasterxml.jackson.core</groupId>                <artifactId>jackson-databind</artifactId>                <version>2.6.0</version>            </dependency>                        <!-- mybatis依赖管理 -->            <!-- mybatis依赖 -->            <dependency>                <groupId>org.mybatis</groupId>                <artifactId>mybatis</artifactId>                <version>3.3.0</version>            </dependency>                        <!-- mybatis和spring整合 -->            <dependency>                <groupId>org.mybatis</groupId>                <artifactId>mybatis-spring</artifactId>                <version>1.2.3</version>            </dependency>                        <!-- mybatis 分页插件 -->            <dependency>                <groupId>com.github.pagehelper</groupId>                <artifactId>pagehelper</artifactId>                <version>4.1.6</version>            </dependency>                        <!-- mysql驱动包依赖 -->            <dependency>                <groupId>mysql</groupId>                <artifactId>mysql-connector-java</artifactId>                <version>5.1.37</version>            </dependency>                        <!-- c3p0依赖 -->            <dependency>                <groupId>com.mchange</groupId>                <artifactId>c3p0</artifactId>                <version>0.9.5</version>            </dependency>                        <!-- redis java客户端jar包 -->            <dependency>                <groupId>redis.clients</groupId>                <artifactId>jedis</artifactId>                <version>2.9.0</version>            </dependency>                        <!-- 文件上传 -->            <dependency>                <groupId>commons-fileupload</groupId>                <artifactId>commons-fileupload</artifactId>                <version>1.3.1</version>            </dependency>                        <dependency>                <groupId>commons-io</groupId>                <artifactId>commons-io</artifactId>                <version>2.5</version>            </dependency>                        <dependency>                <groupId>org.apache.commons</groupId>                <artifactId>commons-email</artifactId>                <version>1.4</version>            </dependency>                        <dependency>                <groupId>org.apache.logging.log4j</groupId>                <artifactId>log4j-core</artifactId>                <version>2.7</version>            </dependency>                        <dependency>                <groupId>org.apache.httpcomponents</groupId>                <artifactId>httpclient</artifactId>                <version>4.5.2</version>            </dependency>                        <dependency>                <groupId>org.apache.httpcomponents</groupId>                <artifactId>httpcore</artifactId>                <version>4.4.4</version>            </dependency>                                    <!-- junit -->            <dependency>                <groupId>junit</groupId>                <artifactId>junit</artifactId>                <version>4.11</version>            </dependency>                        <!-- servlet依赖 -->            <dependency>                <groupId>javax.servlet</groupId>                <artifactId>javax.servlet-api</artifactId>                <version>3.1.0</version>                <scope>provided</scope>            </dependency>            <!-- jsp依赖 -->            <dependency>                <groupId>javax.servlet.jsp</groupId>                <artifactId>jsp-api</artifactId>                <version>2.2</version>                <scope>provided</scope>            </dependency>            <!-- jstl依赖 -->            <dependency>                <groupId>org.glassfish.web</groupId>                <artifactId>jstl-impl</artifactId>                <version>1.2</version>                <exclusions>                    <exclusion>                        <groupId>javax.servlet</groupId>                        <artifactId>servlet-api</artifactId>                    </exclusion>                    <exclusion>                        <groupId>javax.servlet.jsp</groupId>                        <artifactId>jsp-api</artifactId>                    </exclusion>                </exclusions>            </dependency>                                <dependency>            <groupId>net.sf.json-lib</groupId>            <artifactId>json-lib</artifactId>            <version>2.4</version>            <classifier>jdk15</classifier>        </dependency>        <dependency>            <groupId>commons-httpclient</groupId>            <artifactId>commons-httpclient</artifactId>            <version>3.1</version>        </dependency>                      <!-- shiro依赖包 -->        <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-core</artifactId>              <version>1.2.3</version>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-spring</artifactId>              <version>1.2.3</version>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-cas</artifactId>              <version>1.2.3</version>              <exclusions>                  <exclusion>                      <groupId>commons-logging</groupId>                      <artifactId>commons-logging</artifactId>                  </exclusion>              </exclusions>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-web</artifactId>              <version>1.2.3</version>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-ehcache</artifactId>              <version>1.2.3</version>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-quartz</artifactId>              <version>1.2.3</version>          </dependency>        <!-- shiro end -->                                    </dependencies>         <build>        <finalName>sys</finalName>        <plugins>            <!-- 指定JDK编译版本 -->            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                <version>3.1</version>                  <configuration>                    <source>1.8</source>                  <target>1.8</target>                </configuration>            </plugin>        </plugins>    </build></project>2.SSM框架配置beans.xml<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:mvc="http://www.springframework.org/schema/mvc"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop"    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/mvc                        http://www.springframework.org/schema/mvc/spring-mvc.xsd                        http://www.springframework.org/schema/context                        http://www.springframework.org/schema/context/spring-context.xsd                        http://www.springframework.org/schema/aop                        http://www.springframework.org/schema/aop/spring-aop.xsd                        http://www.springframework.org/schema/tx                        http://www.springframework.org/schema/tx/spring-tx.xsd">        <!-- 数据库连接池 -->    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">        <property name="driverClass" value="com.mysql.jdbc.Driver"/>          <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/sys_test?characterEncoding=UTF8&amp;allowMultiQueries=true"/>        <property name="user" value="root"/>          <property name="password" value="root"/>          <property name="maxPoolSize" value="100"/>          <property name="minPoolSize" value="10"/>          <property name="maxIdleTime" value="60"/>      </bean>        <!-- mybatis 的 sqlSessionFactory -->    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">        <property name="dataSource" ref="dataSource"/>        <property name="configLocation" value="classpath:mybatis-config.xml"></property>    </bean>        <!-- mybatis mapper接口自动扫描、自动代理 -->    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">       <property name="basePackage" value="com.sys.mapper" />    </bean>        <!-- 事务管理器 -->    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource" />    </bean>    <!-- 事务传播行为 -->    <tx:advice id="txAdvice" transaction-manager="txManager">        <tx:attributes>            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>            <tx:method name="page*" propagation="SUPPORTS" read-only="true"/>            <tx:method name="is*" propagation="SUPPORTS" read-only="true"/>            <tx:method name="*" propagation="REQUIRED" read-only="false"/>        </tx:attributes>    </tx:advice>    <!-- 织入事务增强功能 -->    <aop:config>        <aop:pointcut id="txPointcut" expression="execution(* com.sys.service..*.*(..))" />        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />    </aop:config>     <!-- 配置扫描spring注解(@Component、@Controller、@Service、@Repository)时扫描的包,同时也开启了spring注解支持 -->     <!-- 这个地方只需要扫描service包即可,因为controller包由springMVC配置扫描,mapper包由上面的mybatis配置扫描 -->    <context:component-scan base-package="com.sys.service"></context:component-scan>    <!-- 开启spring aop 注解支持,要想aop真正生效,还需要把切面类配置成bean -->    <aop:aspectj-autoproxy/>         </beans>dispatcher-servlet.xml<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:mvc="http://www.springframework.org/schema/mvc"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop"    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/mvc                        http://www.springframework.org/schema/mvc/spring-mvc.xsd                        http://www.springframework.org/schema/context                        http://www.springframework.org/schema/context/spring-context.xsd                        http://www.springframework.org/schema/aop                        http://www.springframework.org/schema/aop/spring-aop.xsd                        http://www.springframework.org/schema/tx                        http://www.springframework.org/schema/tx/spring-tx.xsd">                            <!-- 配置扫描spring注解时扫描的包,同时也开启了spring注解支持 -->    <context:component-scan base-package="com.sys" />    <!-- 开启springMVC相关注解支持 -->    <mvc:annotation-driven />        <!-- 开启spring aop 注解支持 -->    <aop:aspectj-autoproxy/>        <!-- 约定大于配置:约定视图页面的全路径 = prefix + viewName + suffix -->    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <property name="prefix" value="/WEB-INF/jsp/"></property>        <property name="suffix" value=".jsp"></property>    </bean>    <!-- 文件上传解析器 -->    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">        <property name="maxUploadSize" value="104857600" />        <property name="defaultEncoding" value="UTF-8" />        <property name="maxInMemorySize" value="40960" />    </bean>        <!-- 资源映射 -->    <mvc:resources location="/css/" mapping="/css/**" />    <mvc:resources location="/js/" mapping="/js/**" />    <mvc:resources location="/images/" mapping="/images/**" />    <mvc:resources location="/skin/" mapping="/skin/**" />    <mvc:resources location="/lib/" mapping="/lib/**" /> </beans>mybatis-config.xml<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <settings>        <!-- 使用log4j2作为日志实现 -->        <setting name="logImpl" value="LOG4J2"/>    </settings>    <typeAliases>        <!-- 为指定包下的pojo类自动起别名 -->        <package name="com.sys.pojo"/>    </typeAliases>        <mappers>        <!-- 自动加载指定包下的映射配置文件 -->        <package name="com.sys.mapper"/>    </mappers></configuration>Log4j2.xml <?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE xml><Configuration status="OFF">    <Appenders>        <Console name="CONSOLE" target="SYSTEM_OUT">            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{0} - %msg%n" />        </Console>        <RollingFile name="ROLLING" fileName="/logs/ups-manager/log.log"             filePattern="/logs/log_%d{yyyy-MM-dd}_%i.log">            <PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>            <Policies>                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>                <SizeBasedTriggeringPolicy size="1024 KB"/>            </Policies>            <DefaultRolloverStrategy max="100"/>        </RollingFile>    </Appenders>        <Loggers>        <Root level="debug">            <AppenderRef ref="CONSOLE" />            <AppenderRef ref="ROLLING"/>        </Root>                <!-- 控制某些包下的类的日志级别 -->        <Logger name="org.mybatis.spring" level="error">            <AppenderRef ref="CONSOLE"/>        </Logger>    </Loggers></Configuration>web.xml<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">    <!-- 初始化spring容器 -->    <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>        <!-- 设置post请求编码和响应编码 -->    <filter>        <filter-name>characterEncodingFilter</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>        <init-param>            <!-- 为true时也对响应进行编码 -->            <param-name>forceEncoding</param-name>            <param-value>true</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>characterEncodingFilter</filter-name>        <!-- 设置为/*时才会拦截所有请求,和servlet有点区别,servlet设置为/*只拦截所有的一级请求,如/xx.do,而不拦截/xx/xx.do;servlet设置为/时才会拦截所有请求 -->        <url-pattern>/*</url-pattern>    </filter-mapping>    <context-param>        <param-name>contextConfigLocation</param-name>        <param-value>            classpath:spring-shiro.xml,            classpath:beans.xml,            classpath:dispatcher-servlet.xml        </param-value>    </context-param>          <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->    <filter>        <filter-name>shiroFilter</filter-name>        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>        <init-param>            <param-name>targetFilterLifecycle</param-name>            <param-value>true</param-value>        </init-param>    </filter>          <filter-mapping>        <filter-name>shiroFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>        <!-- 初始化springMVC容器 -->    <servlet>        <servlet-name>dispatcher</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:dispatcher-servlet.xml</param-value>        </init-param>        <load-on-startup>1</load-on-startup>    </servlet>      <servlet-mapping>        <servlet-name>dispatcher</servlet-name>        <url-pattern>/</url-pattern>    </servlet-mapping>        </web-app>3.实现用户、角色、权限页面操作功能,这里就不贴代码了4.接下来就是shiro,登录认证和授权只需要继承AuthorizingRealm,其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)。身份认证重写doGetAuthenticationInfo方法,授权重写doGetAuthorizationInfo方法。Shiro 默认提供的 Realm 身份认证流程 流程如下:首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。 代码实现:/** * */package com.sys.shiro;import javax.annotation.Resource;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AccountException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.DisabledAccountException;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.subject.SimplePrincipalCollection;import org.apache.shiro.util.ByteSource;import com.sys.pojo.AdminUser;import com.sys.service.AdminUserService;/**  * @ClassName: MyRealm  * @Description: shiro 认证 + 授权   重写 */public class MyRealm extends AuthorizingRealm {    @Resource    AdminUserService adminUserService;            /* (non-Javadoc)     * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)     */    /**     * 授权Realm     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {        String account = (String)principals.getPrimaryPrincipal();        AdminUser pojo = new AdminUser();        pojo.setAccount(account);        Long userId = adminUserService.selectOne(pojo).getId();        SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();        /**根据用户ID查询角色(role),放入到Authorization里.*/        info.setRoles(adminUserService.findRoleByUserId(userId));        /**根据用户ID查询权限(permission),放入到Authorization里.*/        info.setStringPermissions(adminUserService.findPermissionByUserId(userId));        return info;    }    /* (non-Javadoc)     * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)     */    /**     * 登录认证Realm     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {        String username = (String)token.getPrincipal();        String password = new String((char[])token.getCredentials());        AdminUser user = adminUserService.login(username, password);        if(null==user){            throw new AccountException("帐号或密码不正确!");        }        if(user.getIsDisabled()){            throw new DisabledAccountException("帐号已经禁止登录!");        }        //**密码加盐**交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配        return new SimpleAuthenticationInfo(user.getAccount(),user.getPassword(),ByteSource.Util.bytes("3.14159"), getName());    }        /**     * 清空当前用户权限信息     */    public  void clearCachedAuthorizationInfo() {        PrincipalCollection principalCollection = SecurityUtils.getSubject().getPrincipals();        SimplePrincipalCollection principals = new SimplePrincipalCollection(                principalCollection, getName());        super.clearCachedAuthorizationInfo(principals);    }    /**     * 指定principalCollection 清除     */    public void clearCachedAuthorizationInfo(PrincipalCollection principalCollection) {        SimplePrincipalCollection principals = new SimplePrincipalCollection(                principalCollection, getName());        super.clearCachedAuthorizationInfo(principals);    }        }shiro的配置:spring-shiro.xml<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"    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/tx http://www.springframework.org/schema/tx/spring-tx.xsd       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">        <!--shiro 核心安全接口  -->        <property name="securityManager" ref="securityManager"></property>        <!--登录时的连接  -->        <property name="loginUrl" value="/login"></property>              <!--未授权时跳转的连接  -->        <property name="unauthorizedUrl" value="/unauthorized.jsp"></property>           <!-- 其他过滤器 -->           <property name="filters">               <map>                   <!-- <entry key="rememberMe" value-ref="RememberMeFilter"></entry> -->                   <entry key="kickout" value-ref="KickoutSessionControlFilter"/>               </map>           </property>                      <!-- 读取初始自定义权限内容-->        <!-- 如果使用authc验证,需重写实现rememberMe的过滤器,或配置formAuthenticationFilter的Bean -->        <property name="filterChainDefinitions">            <value>                /js/**=anon                /css/**=anon                /images/**=anon                /skin/**=anon                   /lib/**=anon                   /nodel/**=anon                   /WEB-INF/jsp/**=anon                   /adminUserLogin/**=anon                                                             /**/submitLogin.do=anon                /**=user,kickout            </value>        </property>    </bean>                    <!-- Shiro生命周期处理器-->    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />        <!-- 安全管理器 -->    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">        <property name="realm" ref="MyRealm"/>        <property name="rememberMeManager" ref="rememberMeManager"/>    </bean>        <bean id="MyRealm" class="com.sys.shiro.MyRealm" >        <property name="cachingEnabled" value="false"/>    </bean>        <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>        <property name="arguments" ref="securityManager"/>    </bean>        <!-- sessionIdCookie:maxAge=-1表示浏览器关闭时失效此Cookie -->    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">          <constructor-arg value="rememberMe"/>          <property name="httpOnly" value="true"/>          <property name="maxAge" value="-1"/>      </bean>            <!-- 用户信息记住我功能的相关配置 -->    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">        <constructor-arg value="rememberMe"/>        <property name="httpOnly" value="true"/>        <!-- 配置存储rememberMe Cookie的domain为 一级域名        这里如果配置需要和Session回话一致更好。-->        <property name="maxAge" value="604800"/><!-- 记住我==保留Cookie有效7天 -->    </bean>        <!-- rememberMe管理器 -->    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">        <!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->        <property name="cipherKey"                  value="#{T(org.apache.shiro.codec.Base64).decode('3AvVhmFLUs0KTA3Kprsdag==')}"/>        <property name="cookie" ref="rememberMeCookie"/>    </bean>        <!-- 记住我功能设置session的Filter -->    <bean id="RememberMeFilter" class="com.sys.shiro.RememberMeFilter" />        <!-- rememberMeParam请求参数是 boolean 类型,true 表示 rememberMe -->    <!-- shiro规定记住我功能最多得user级别的,不能到authc级别.所以如果使用authc,需打开此配置或重写实现rememberMe的过滤器 -->    <!-- <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">        <property name="rememberMeParam" value="rememberMe"/>    </bean> -->            <bean id="KickoutSessionControlFilter" class="com.sys.shiro.KickoutSessionControlFilter">    </bean>                  </beans>5.登录即密码失败多次后锁定/** * */package com.sys.controller;import java.util.LinkedHashMap;import java.util.Map;import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AccountException;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.DisabledAccountException;import org.apache.shiro.authc.ExcessiveAttemptsException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;import com.sys.pojo.AdminUser;import com.sys.service.AdminUserService;import com.sys.common.JedisUtils;import com.sys.shiro.ShiroUtils;/**  * @ClassName: LoginController  * @Description: 登录*/@Controllerpublic class LoginController{    protected final static Logger logger = LogManager.getLogger(LoginController.class);    protected Map<String, Object> resultMap = new LinkedHashMap<String, Object>();        @Resource    AdminUserService adminUserService;        /**    * @Description: 登录认证    * @param um 登录账号    * @param pw 登录密码    * @param rememberMe 记住我    * @param request    * @return    * @throws      * @author lao    * @Date 2018年1月15日下午12:24:19    * @version 1.00     */    @RequestMapping(value="/submitLogin.do",method=RequestMethod.POST)    @ResponseBody    public Map<String,Object> submitLogin(String um,String pw,boolean rememberMe,HttpServletRequest request){                Subject subject = SecurityUtils.getSubject();        UsernamePasswordToken token = new UsernamePasswordToken(um,ShiroUtils.getStrByMD5(pw));                            try{                      token.setRememberMe(rememberMe);            subject.login(token);            JedisUtils.del(um);            logger.info("------------------身份认证成功-------------------");            resultMap.put("status", 200);            resultMap.put("message", "登录成功!");        } catch (DisabledAccountException dax) {              logger.info("用户名为:" + um + " 用户已经被禁用!");            resultMap.put("status", 500);            resultMap.put("message", "帐号已被禁用!");        } catch (ExcessiveAttemptsException eae) {              logger.info("用户名为:" + um + " 用户登录次数过多,有暴力破解的嫌疑!");            resultMap.put("status", 500);            resultMap.put("message", "登录次数过多!");        } catch (AccountException ae) {              logger.info("用户名为:" + token.getPrincipal() + " 帐号或密码错误!");            String excessiveInfo = ExcessiveAttemptsInfo(um);            if(null!=excessiveInfo){                resultMap.put("status", 500);                resultMap.p
06-17
2017
有关Servlet和JSP的梳理
大二第一学期的时候有学JSP的课,但是因为在开学之前做过JSP的小项目,所以一个学期的课也没听,直到期末考试成绩出来了,才回想JSP的内容还有多少记得,没想到模模糊糊也记不起多少,赶紧回头学回来。接下来是关于Servlet和JSP的梳理。-------------------------------------------------------------------------------------------------------------------------------------------------  Servlet是一个Java程序,一个Servlet应用有一个或多个Servlet程序,而且JSP页面也会被转换和编译成Servlet程序。  Servlet应用无法独立运行,必须运行在Servlet容器中。Servlet容器将用户的请求传递给Servlet应用,并将结果返回给用户。由于大部分Servlet应用都包含多个JSP页面,因此更准确地说是“Servlet/JSP应用”。  其中,Servlet API是开发Servlet的主要技术。而Servlet API有以下4个Java包:Java包包含的内容javax.servlet定义Servlet和Servlet容器之间契约的类和接口javax.servlet.http定义HTTP Servlet和Servlet容器之间契约的类和接口javax.servlet.annotation标注Servlet、Filter、Listener的标注。它还被标注原件定义元数据javax.servlet.descriptor包含提供程序化登陆web应用程序的配置信息的类型。  Servlet技术的核心是Servlet,它是所有Servlet类必须直接或间接实现的一个接口,而Servlet接口定义了Servlet与Servlet容器之间的契约。这个契约归结起来就是,Servlet容器将Servlet类载入内存,并在Servlet实例上调用具体的方法。当用户的请求使得Servlet容器调用Servlet的Service方法,会传入一个ServletRequest实例和一个ServletResponse实例,其中,ServletRequest中封装了当前的HTTP请求,而ServletResponse表示当前用户的HTTP响应。对于每一个应用程序,Servlet容器还会创建一个ServletContext实例,这个对象中封装了上下文(应用程序)的环境详情。每个上下文只有一个ServletContext,而且每个Servlet实例都有一个配置的ServletConfig。  Servlet的生命周期方法:  Servlet容器的生命周期方法作用init该方法在Servlet第一次被请求的时候,Servlet就会调用这个方法,而后不再被调用,所以可以用这个方法进行初始化工作。调用这个方法时,Servlet容器会传入一个ServletConfig。Service每当请求Servlet时,Servlet容器就会调用这个方法。第一次请求Servlet时,Servlet容器调用init方法和Service方法。后续的请求将只调用Service方法。destroy当要销毁Servlet时,Servlet容器就会调用这个方法。一般会在这个方法中编写清除代码。  在介绍一个Servlet中另外两个非生命周期的方法:Servlet容器的非生命周期方法作用getServletInfo这个方法会返回Servlet的描述getServletConfig这个方法会返回由Servlet容器传给init方法的ServletConfig。但是为了让getServletConfig返回一个非null值,必须将传给init方法的ServletConfig赋给一个类级变量。-------------------------------------------------------------------------------------------------------------------------------------------------   接下来是Servlet的各类接口:  ServletRequest 接口说明ServletRequest对于每一个HTTP请求,Servlet容器都会创建一个ServletRequest实例,并将它传给Servlet的Service方法。ServletRequest封装了关于这个请求的信息。  常用的方法有:    方法说明public int getContentLength()返回请求主体的字节数。public java.lang.String getContentType()返回请求主体的MIME类型。public java.lang.String getParameter(java.lang.String name)返回指定请求参数的值public java.lang.String getProtocol()返回这个HTTP请求的协议名称和版本  ServletResponse接口说明ServletResponse该接口表示一个Servlet响应。在调用Servlet的Service方法前,Servlet容器首先创建一个ServletResponse,并将它作为第二个参数传给Service方法。ServletResponse隐藏了向浏览器发送响应的复杂过程。  常用的方法: 方法说明getWriter()返回了一个可以向客户端发送文本的java.io.PrintWriter。默认情况下,PrintWriter对象使用ISO-8859-1编码。setContentType(“type”)设置响应的内容类型,并将”text/html”作为一个参数传入。如果没有设置相应内容类型,有些浏览器就会将HTML标签显示为普通文本。  ServletConfig  接口说明ServletConfig用于存储关于Servlet的配置信息。当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init()方法传入一个ServletConfig。ServletConfig封装可以通过@WebServlet或描述符传给Servlet的配置信息。   常用的方法: 方法说明java.lang.String getInitParameter(java,lang.String name)为了从Servlet内部获取到初始参数的值,要在Servlet容器传给Servlet的init方法的ServletConfig中调用getInitParameter方法。java.util.Enumration<java.lang.String> getInitParameterNames()返回所有初始参数名称的一个Enumeration。String contactName = servletConfig.getInitParameter(“contactName”)获取contactName参数值  ServletContext接口说明ServletContext每个Web应用程序只有一个上下文,所以ServletContext可以存储这个上下文,并且ServletContext还可以共享从应用程序中的所有资料处访问到的信息,并且可以动态注册Web对象,而且是用ServletContext内部的Map保存。  常用的方法: 方法说明getServletContext().getInitParameter(String name)获取在项目下的web.xml中设置context的初始化参数this.getServletContext().log(“测试”)在web.xml文件中,使用logger元素来设置日志文件getAttribute(String name)/get AttributeNames()获取ServletContext中的属性setAttribute(String name, Object object)设置ServletContext中的属性removeAttribute(String name)移除ServletContext中的属性  GenericServlet接口说明GenericServletGenericServlet实现了Servlet和ServletConfig接口。将init方法中的ServletConfig赋给一个类级变量,以便可以通过getServletConfig获取,为Servlet接口中的所有方法提供默认的实现,而且提供包括ServletConfig中的方法。  Http Servlets    接口说明HttpServletHttpServlet类覆盖了javax.servlet.GenericServlet类。使用HttpServlet时,还要借助分别代表Servlet请求和Servlet响应的HttpServletRequest和HttpServletResponse对象。而HttpServlet与GenericServlet的差别在于,HttpServlet覆盖的是doGet或者doPost方法,而不是Service方法,而且使用的是HttpServletRequest和HttpServletResponse,而不是ServletRequest和ServletResponse。HttpServletRequest表示HTTP环境中的Servlet请求HttpServletResponse表示HTTP环境中的Servlet响应  各个接口常用的方法如下:  接口方法说明 HttpServletRequestjava.lang.String getContextPath()返回表示请求上下文的请求URI部分。 HttpServletRequestCookie[] getCookies()返回一个Cookie对象的数组。 HttpServletRequestjava.lang.String getHeader(java.lang.String name)返回指定HTTP标题的值。 HttpServletRequestjava.lang.String getMethod()返回生成这个请求的HTTP方法名称 HttpServletRequestjava.lang.String getQueryString()返回请求URL中的查询字符串。 HttpServletRequestHttpSession getSession()返回与这个请求相关的会话对象。如果没有,将创建一个新的会话对象。 HttpServletRequestHttpSession getSession(Boolean create)返回与这个请求相关的绘画对象,如果有,并且create参数为True,将创建一个新的会话对象。 HttpServletResponsevoid addCookie(Cookie cookie)给这个响应对象添加一个cookie。 HttpServletResponsevoid addHeader(java.lang.String name, java.lang.String value)给这个响应对象添加一个header。 HttpServletResponsevoid sendRedirect(java.lang.String location)发送一条响应码,将浏览器跳转到指定的位置。-------------------------------------------------------------------------------------------------------------------------------------------------  当用户提交HTML表单时,在表单元素中输入的值就会被当作请求参数发送到服务器。HTML输入域(文本域、隐藏域或者密码域)或者文本区的值,会被当作字符串发送到服务器。空的输入域或者文本区会发送空的字符串。  包含多个值的select元素发出一个字符串数组,可以通过ServletRequest.getParameterValues进行处理。  核查过的复选框会发送字符串"on"到服务器,未经核查的复选框则不向服务器发送任何内容,ServletRequest.getParameter(fieldName)返回null。  单选框将被选中按钮的值发送到服务器。如果没有选择任何按钮,将没有任何内容被发送到服务器,并且ServletRequest.getParameter(fieldName)返会null。  如果一个表单中包含多个输入同名的元素,那么所有值都会被提交,并且必须利用ServletRequest.getParameterValues来获取它们。ServletRequest.getParameter将只返回最后一个值。
01-17
2018
WebAPI 实现前后端分离
随着Web技术的发展,现在各种框架,前端的,后端的,数不胜数。全栈工程师的压力越来越大。现在的前端的框架,既可以做各种Web,又可以做各种APP,前端框架更新换代越来越快,越来越多。传统的模式前端和后端进行调试,修改都非常麻烦。往往前端配合后端很痛苦,后端也嫌前端麻烦。(无解,能动手解决的事,尽量别动嘴。办公室应该常备一些,绷带,止血条,速效救心丸等药品。为了阻止事态升级,办公室要加强刀具管制条例。)前后端分离前端根据事先约定好的文档,可以自己摸拟数据,然后开发,测试,调试UI,发布到线上时把API接口改成线上API接口,即可完事。前端日后增加新功能,修改UI,自己修改,自己编译更新自己UI站点,发布线上只要调上线上API接口即可。并不需要麻烦到后端。两者工作进行分离。后端需要跟前端商量好接口,写好接口文档,在接口功能上相互沟通(其实相当于需求相互沟通),一旦接口文档订好之后,只需按事先约定实现API接口即口。把项目编译好发布到线上服务器。即可完事。后端实现WebApi接口,还可以面对各种调用,如PC端web,手机APP,或者其它设备。一个接口多种调用,实现代码去重。工作模式分析对前端和后端进行分离。各司其职,各自在自己的领域集中精力研究。更能有效的加深技术深度。 前后端分离的模式,你需要N名前端工程师和N名后端工程师。首先我们要约定一些返回基本的格式,比如用XML,还是JSON。结果大多数前端都是喜欢JSON,因为JS天生就支持JSON。我贴出一些示例代码  {    "ResultCode": 1300,    "Message":"权限不足",    "Date":null,   }: ::::返回参数说明参数名类型是否必有说明ResultCodeint是返回码Messagestring是结果说明DetailErrorjosn否具体错误Datejosn否数据        ResultCodeResultCode说明1000成功1100服务器异常1200身份验证异常1300权限不足1400传递参数验证不通过1500版本异常1600业务逻辑异常1700系统成升级中1800该接口己弃用          具体异常这是一个有点争议的地方,有很多业务逻辑异常,出于对用户的友好提示。一些生涩难懂的错误提示,直接给到用户,用户一脸懵逼。但是后端却不能修改成友好提示,这样不方便调试,寻找问题原因。一般来讲,前端可以自动修改友好提示给用户。如果后端返回字符串,前端写死在代码中,万一,某一天后端认为这个描述更符合场景,修改的字符串。敌军还有30秒到达战场。建议:尽量使用异常代码,大家可以看到上面贴出例子,就使用的异常代码。每种异常都有唯一编号,描述可以更改。但是编号不变。用户异常(1601000)说明1601001账号/密码错误1601002账号被冰冻1601003原密码不对    版本控制 每个API都有一个版本,其实也是就针对APP,如果是WEB端的,都是直接升级的因为B/S结构本身就是存在升级方便的优势,只需要把服务端更新就可以了。版本控制一般用两种方式第一种:URL不变,版本写在HTTP标头内面。第二种:版本写在URL上面。本人推荐第二种,比较直接方便了解。示例:http://www.xxx.com/版本号当前版本号:v1 http://www.baidu.com/v1/UserSecurity/LoginAPI风格现在流行的api风格比较多,最出名的就是restful风格。按本人的经验,完全走restful风格是很困难的,可能也是水平问题,在团队内面也要考虑到其它成员的水平问题。我们目前API风格还是保留以前风格。示例,V*代表版本号http://xx.com/V*/UserSecurity/SignOutHTTP谓词使用 Post 方法在服务器上创建/修改/删除资源使用 Get 方法从服务器检索某个资源或者资源集合基本命名规则使用骆驼式命名法-大驼峰法跨域处理前端站点和后端API布署到不同的站点,就会产生跨域问题。什么是同源策略?同源是域名,协议,端口相同。也就是说如果不同,则是非同源。同源策略是浏览器的一基本的安全功能,非同源访问,浏览器会进行拒绝。HMTL上面的SRC地址,你可以指定任何URL,表单提交,你可以提交到任何URL。但是,你如果使用AJAX技术,就会受到同源策略的影响,拒绝提交。现代浏览器几乎都支持跨域资源请求的一种方式。这种技术叫CORS(跨域资源共享)CORS跨域分两种第一种,简单跨域。第二种,复杂跨域。解决方案:HTTP输出标头增加如何节点注意有前端框架版本,对安全要求较高,不能使用通配符*,要指定跨域域名。Access-Control-Allow-Origin:* 下面节点可填,可不填,根据实际情况,自行决定。123Access-Control-Allow-Methods:GET,POST,OPTIONSAccess-Control-Allow-Credentials:trueAccess-Control-Allow-Headers:根据请求头的内容,填写  注意:复杂跨域比要简单跨域麻烦,更花费性能。因为复杂跨域在请求之前会先发一个options预请求,根据响应判断服务器是否支持跨域。也就是说,实际上请求了两次。Cookies作用域不同的站点,如何通用Cookies?一般情况只需把cookies作用域设置顶级域名,浏览器会自动把cookies在访问子域名的时候捎上去。示例,访问二级域名时候,cookies默认会被传送过去。顶级域名:baidul.com cookies作用域:.baidu.com 二级域名: www.baidu.com api.baidu.com 示例下面贴一些示例文档,其它的就不多讲啦 基本上,WebApi前后端分离的细节和注意点,都记录下来,还有更多的细节,需要读者在开发过程自己去寻找答案。随笔完毕!
01-17
2018
我用Python玩小游戏“跳一跳”,瞬间称霸了朋友圈!
从前几天微信最新版本 6.6.1 的更新开始,微信小程序游戏“跳一跳”似乎在一夜之间风靡了朋友圈。它甚至比五六年前的飞机大战游戏都火爆,这种小游戏的火爆不仅仅是因为有魔性、有意思,更重要的是可以进行好友 PK!“跳一跳”的小游戏推出后,很多准备奋发向上的同学,这个假期的美好愿景被毁了。为了多跳几步,以及朋友圈的排名,大家在整个假期都是这样的:就这样跳啊跳...挤地铁跳,蹲马桶跳,乘电梯跳,静默的每 1 秒都不能浪费在办公室,还要时刻警觉后面...说好的工作呢...我控制不住我自己啊!可是很多人费尽心思跳了一下午也没超过 100 分但排行榜里四分之三的人都超过三位数了……真是扎心了……今天小编来告诉你,如何才能获取高分,如何才能占据朋友圈榜首?游戏攻略拿高分普通版本的高分秘籍是这样的:如果你每次都能挑到各自的正中间的话,可以 + 2 分,如果连着跳到中间会 + 4、+6、+8、+10……跳到污水井盖上面,停留 2 秒,等到下水道声音响起直接 + 5 分跳到魔方上面,停留 2 秒,等到魔方转正会直接 + 10 分跳到音乐盒上面,停留 2 秒,等到音乐响起会直接 + 30 分跳到便利店,停留 2 秒,等到便利店开门会直接 + 15 分以上是针对普通用户,但对咱们程序猿来说用这套太 Low 了,接下来要说的是如何从技术层面去实现高分:技术手段实现高分通过 Python 手段在 Github 上面已经有人用 Python 来玩跳一跳这个游戏了,想多少分就有多少分。GitHub 地址:https://github.com/wangshub/wechat_jump_game步骤:安卓手机打开 USB 调试,设置》开发者选项》USB 调试。电脑与手机 USB 线连接,确保执行 adb devices 可以找到设备 id。界面转至微信跳一跳游戏,点击开始游戏。运行 python wechat_junp_auto.py,如果手机界面显示 USB 授权,请点击确认。很有趣!简单点说就是:用电脑帮你玩微信跳一跳,全自动,不用手动。效果:这里梳理一份稍微完整一点的操作步骤,以 Mac 的为例,Win 的思路是一样的。另外,这里用的是安卓手机,iOS 也差不多,不过要下载一个 5.5GB 的 Xcode。1、下载程序,打开下面的链接,点右侧 clone or download,再点 download zip。2、解压 zip 文档,再把文件夹挪到桌面,打开文件夹,你会看到很多东东:3、打开 mac 系统自带的“终端”,这是一个命令行应用,win 用 cmd 就可以了吧。4、通过终端进入文件夹,命令行如下:~/Decktop/wechat_jump_game-master5、安装 pip,在终端输入 sudo easy_install pip 再回车,可能要输入密码。6、安装各种依赖程序,在终端输入 pip install -r requirements.txt 再回车,系统会自动安装。requirements.txt 就是文件夹里的一个 txt 文档,里面写着会自动安装哪些程序。pip 就是第 5 步安装的程序,如果没安装,pip install -r requirements.txt 将无法执行。7、安装 adb,打开下面的链接查看,有 3 种方法,建议用第二种,是英文,如果你不懂英文可以百度中文教程。https://stackoverflow.com/questions/31374085/installing-adb-on-mac-os-x8、打开安卓手机的设置 - 开发者选项 - USB 调试(如果没有开发者选项,可百度打开开发者选项的方法),用 USB 线连接手机和电脑,手机可能会弹出对话框,点同意。如果出现运行脚本后小人不跳的情况,请检查是否有打开“USB 调试(安全模式)”,记得顺便打开 USB 模拟点击。9、在终端输入 adb devices,如果看到下面这种信息,说明 adb 已正确安装,也说明电脑成功检测到手机。如果你系统是 Win10 或 Win8 可能需要先设置一下“禁用强制驱动程序签名”。不然会出现下面的“文件的哈希值不在指定目录中”安装不上 adb 驱动的问题,网上有教程请自行学习。10、打开微信跳一跳点开始,在终端输入 python wechat_jump_auto.py 点回车,游戏就会自动开始~ 请根据手机分辨率运行相应的 *.py 文件。注意:我跳了很多次,最后都会掉下盒子,暂时最多只能跳到 1800+ 分,不能一直跳下去。分辨率不同,配置文件也不一样,具体看 config 这个文件夹。别刷太高分,有人刷到 4000,结果分数被微信清零。实验结果:只要有耐心,你就是王者下面分析一下代码,Main 部分有一个 While 循环,只要你不终止,它会一直重复操作。Main部分代码里面主要调用的自定义函数有三个,还有一个 time.sleep 是为了延迟一下:pull_screenshot() #获取图像find_piece_and_board(im) #根据图像获取两个点的坐标值jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2))#根据两点距离和手机像素计算按压时间并 JUMPpull_screenshot()这个函数主要是利用 adb 来获取图像,这里顺便说一下“adb”,adb 是连接 Android 手机与 PC 端的桥梁,可以让用户在电脑上对手机进行全面的操作。借助 adb 工具,我们可以管理设备,还可以进行如安装软件、系统升级、运行 Shell 命令等等操作。如“pull”就是获取设备中的文件,想更多了解 ADB 请自行学习~find_piece_and_board()根据图像获取当前小人位置和落点的坐标系(piece_x, piece_y, board_x, board_y),这个是这个脚本中的核心部分。jump根据设定的“长按的时间系数”计算需要的按压时间,这个系数是根据手机分辨率推出来的,按压时间设定不小于 200ms,核心命令是 adb 的“input swipe”。“input swipe”模拟的是手指在屏幕上的滑动事件,如果两个点坐标不变化就成了长按了。代码中四个变量的设置是:“swipe_x1,swipe_y1,swipe_x2,swipe_y2 = 320,410,320,410”,所以是模拟的长按,其实滑动也是可以的。伪造 POST 请求刷分除了可以用 Python 实现高分,还有网友爆料还可以直接伪造 POST 请求刷分,直接改分数。昨日,V2EX 网站上一篇题为《微信跳一跳 可以直接更改分数, POST 请求没有校验… 》的文章获得大量曝光,帖中指出微信小程序存在漏洞,跳一跳小游戏可以直接改分数。用户朱鹏飞根据帖子的指引,发现甚至连微信小程序、小游戏的源代码都可以直接下载,只需要知道 appid 和版本号,就可以直接构造 URL 下载后缀为 wxapkg 的源码包,不需要任何验证。据微信公众号“小专栏平台”消息,截自 1 月 1 日 23:50,微信官方已经修复了这个漏洞。不过,据说一些老版本的微信还是可以抓包获取包地址。最后一个微信已经修复 Bug(部分版本没有修复),但只要利用好前面两个攻略,再配合对节奏的把握,登上朋友圈前几完全不在话下。话不多说,赶紧去玩吧!!!
01-17
2018
2018,怎么缓解大数据的尴尬
关于大数据,最近爆出的一个笑话:在电影业一次内部行业会议上,一位巨无霸级别的电影业发言人说:通过数据挖掘,我们发现不同观众的相关卖品偏好。比如《芳华》的观众比《战狼Ⅱ》观众消费了更多的热饮。这些都是之前我们不知道的,也是无法预测的。上面这样一个基于两部影片的观影数据分析得出来的结论,看似客观正确,实则因为模型不完善(缺少观影季节的考量)等原因,而闹出笑话。在近期,我们在给金融科技做盘点的时候,就发现大数据自身就是一个“尴尬”。我们找遍新闻,也没有发现这个词有什么特别值得说道的地方。只能靠着一点时政资料凑齐了这个关键词的盘点。2017年,大数据如此重要,却又如此没有料。大数据模型不完善,是因为根基不牢大数据一直不温不火,和他的发展缺陷有很大的关系。虽然大家极力看好它,但未能迎来行业的爆发。和一些做大数据的朋友聊天,他们甚至会很直白地吐槽自己家的数据模型。“那些所谓的数据模型之类的鬼东西,你只需瞄上一眼,就能头疼一整天。模型里的数据巨大无比,线索逻辑纷繁复杂。很多数据看似很重要却极其无聊,对结果判断毫无意义,食之无味弃之可惜,鸡肋一般的存在。”“说实在的,根本原因不在于技术的落后,而是整个行业的发展根基太浅,无法对数据的有效性进行勘误、归纳和合理解释。”“粗略地说,合理的大数据架构是,数据模型完善,能根据特定领域做出全面合理的数据精简,去掉无关数据和干扰数据,梳理出一条合理的客观建议,并根据数据分析师的主观判断和勘误,再总结出合理的结论,对相关行业做出准确的预判。”“现在呢?本来数据模型都存在这样和那样的漏洞,却还想着数据处理的完全自动化。”“而完全依靠客观数据,完成所谓的人工智能演算,那都是扯淡的事儿。”“刚才说的那个《芳华》和《战狼Ⅱ》的笑话其实就是一个看似客观,实则可笑的分析结论。”“这是因为,大家一说到大数据,就太拿数据想当然了。如果只靠着这点意识去做消费金融领域的数据分析,肯定有很多投资人被坑得底儿朝天!”“所以现在挣钱的还是那些靠着倒买倒卖用户资料的数据公司,一个数据包,加点水分,到处卖,收益无限。”“不过,最近似乎也没那么容易整了,因为官方越查越严,有些所谓的大数据公司搞不动了,怕是要凉了。”物联网或许是大数据公司的真正机会“除了行业经验的累积,还需要更多数据做线上支撑。”“当然,并不是说数据越多越好,而是说,线上的数据越丰富,越有利于我们组织有效数据。”“核心问题就在于,如何产生大量的有效数据。”“有效数据,简单了说,就某个领域,比如,消费金融领域的某一个小细分的消费品的相关数据,在合理组合和解构之后,对行业发展做出合理预判,对投资人预期负责的数据。否则,数据越大,负担越重,越成不了事儿。”积累经验到什么时候才算是个头呢?“或许要等到物联网时代的真正到来。”为什么?“物联网可以让更多的消费金融数据和物流数据线上化,个人消费信用信息也将进一步线上化,数据的归集和处理将更加高效和全面。”“不过,随着移动支付的快速发展,更多人的金融消费能力在线上就基本被呈现了出来,包括个人的消费习惯和个人征信信息都被线上化,而由此产生的物流信息、住房、贷款信息等都在逐步完成终极线上化,这些对大数据来说,都是极好的机会。”“大数据行业机会很大,但大数据是一个不稳定的行业,因为一切的数据都归结到机器里,而机器由人来掌控,相关的操作风险完全看自己的风险意识和人品。行业随时爆发大规模风险,运气好只影响数据安全,运气不好,很企业和个人的信用会破产。这会给行业,甚至整个社会带来巨大的灾难。”“因此,从业企业的相关准则需要进一步细化和规范,对人也需要有个职业操守方面的管制。”什么样的人怎么用数据,其目的和效果都是不一样的。这又和一个大数据相关的段子有点关系,正好段子开头,笑话结尾,也还算圆满。俺家钟点工说:“俺儿子又被老师训了。”俺问又咋啦?她说:学校请了个政法大学的教授来给孩子们讲课,说还是个名人呢,见天在电视上忽悠。他告诫孩子们不要打架,他说他统计过,打架斗殴死了的人百分之九十五以上都是先动手那个,然后问孩子们这是为什么?俺儿子说因为没死的说是死了的先动手的。
01-17
2018
解Bug之路:记一次JVM堆外内存泄露Bug的查找
前言JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔者将此Bug分析的过程写成博客,以飨读者。由于物理内存定量分析部分用到了linux kernel虚拟内存管理的知识,读者如果有兴趣了解请看ulk3(《深入理解linux内核第三版》)内存泄露Bug现场一个线上稳定运行了三年的系统,从物理机迁移到docker环境后,运行了一段时间,突然被监控系统发出了某些实例不可用的报警。所幸有负载均衡,可以自动下掉节点,如下图所示:登录到对应机器上后,发现由于内存占用太大,触发OOM,然后被linux系统本身给kill了。应急措施紧急在出问题的实例上再次启动应用,启动后,内存占用正常,一切Okay。奇怪现象当前设置的最大堆内存是1792M,如下所示:-Xmx1792m -Xms1792m -Xmn900m -XX:PermSi ze=256m -XX:MaxPermSize=256m -server -Xss512k查看操作系统层面的监控,发现内存占用情况如下图所示:上图蓝色的线表示总的内存使用量,发现一直涨到了4G后,超出了系统限制。很明显,有堆外内存泄露了。查找线索gc日志一般出现内存泄露,笔者立马想到的就是查看当时的gc日志。本身应用所采用框架会定时打印出对应的gc日志,遂查看,发现gc日志一切正常。对应日志如下:查看了当天的所有gc日志,发现内存始终会回落到170M左右,并无明显的增加。要知道JVM进程本身占用的内存可是接近4G(加上其它进程,例如日志进程就已经到4G了),进一步确认是堆外内存导致。排查代码打开线上服务对应对应代码,查了一圈,发现没有任何地方显式利用堆外内存,其没有依赖任何额外的native方法。关于网络IO的代码也是托管给Tomcat,很明显,作为一个全世界广泛流行的Web服务器,Tomcat不大可能有堆外内存泄露。进一步查找由于在代码层面没有发现堆外内存的痕迹,那就继续找些其它的信息,希望能发现蛛丝马迹。Dump出JVM的Heap堆由于线上出问题的Server已经被kill,还好有其它几台,登上去发现它们也 占用了很大的堆外内存,只是还没有到触发OOM的临界点而已。于是就赶紧用jmap dump了两台机器中应用JVM的堆情况,这两台留做现场保留不动,然后将其它机器迅速重启,以防同时被OOM导致服务不可用。使用如下命令dump:jmap -dump:format=b,file=heap.bin [pid]使用MAT分析Heap文件挑了一个heap文件进行分析,堆的使用情况如下图所示:一共用了200多M,和之前gc文件打印出来的170M相差不大,远远没有到4G的程度。不得不说MAT是个非常好用的工具,它可以提示你可能内存泄露的点:这个cachedBnsClient类有12452个实例,占用了整个堆的61.92%。查看了另一个heap文件,发现也是同样的情况。这个地方肯定有内存泄露,但是也占用了130多M,和4G相差甚远。查看对应的代码系统中大部分对于CachedBnsClient的调用,都是通过注解Autowired的,这部分实例数很少。唯一频繁产生此类实例的代码如下所示:@Override     public void fun() {             BnsClient bnsClient = new CachedBnsClient();           // do something     return  ; }此CachedBnsClient仅仅在方法体内使用,并没有逃逸到外面,再看此类本身public class CachedBnsClient   {     private ConcurrentHashMap<String, List<String>> authCache = new ConcurrentHashMap<String, List<String>>();     private ConcurrentHashMap<String, List<URI>> validUriCache = new ConcurrentHashMap<String, List<URI>>();     private ConcurrentHashMap<String, List<URI>> uriCache = new ConcurrentHashMap<String, List<URI>>(); ...... }没有任何static变量,同时也没有往任何全局变量注册自身。换言之,在类的成员(Member)中,是不可能出现内存泄露的。当时只粗略的过了一过成员变量,回过头来细想,还是漏了不少地方的。更多信息由于代码排查下来,感觉这块不应该出现内存泄露(但是事实确是如此的打脸)。这个类也没有显式用到堆外内存,而且只占了130M,和4G比起来微不足道,还是先去追查主要矛盾再说。使用jstack dump线程信息现场信息越多,越能找出蛛丝马迹。先用jstack把线程信息dump下来看下。 这一看,立马发现了不同,除了正常的IO线程以及框架本身的一些守护线程外,竟然还多出来了12563多个线程。"Thread-5" daemon prio=10 tid=0x00007fb79426e000 nid=0x7346 waiting on condition [0x00007fb7b5678000]    java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.xxxxx.CachedBnsClient$1.run(CachedBnsClient.java:62)而且这些正好是运行再CachedBnsClient的run方法上面!这些特定线程的数量正好是12452个,和cachedBnsClient数量一致!再次check对应代码原来刚才看CachedBnsClient代码的时候遗漏掉了一个关键的点!    public CachedBnsClient(BnsClient client) {         super();         this.backendClient = client;         new Thread() {             @Override             public void run() {                 for (; ; ) {                     refreshCache();                     try {                         Thread.sleep(60 * 1000);                     } catch (InterruptedException e) {                         logger.error("出错", e);                     }                 }             }     }这段代码是CachedBnsClient的构造函数,其在里面创建了一个无限循环的线程,每隔60s启动一次刷新一下里面的缓存!找到关键点在看到12452个等待在CachedBnsClient.run的业务的一瞬间笔者就意识到,肯定是这边的线程导致对外内存泄露了。下面就是根据线程大小计算其泄露内存量是不是确实能够引起OOM了。发现内存计算对不上由于我们这边设置的Xss是512K,即一个线程栈大小是512K,而由于线程共享其它MM单元(线程本地内存是是现在线程栈上的),所以实际线程堆外内存占用数量也是512K。进行如下计算:12563 * 512K = 6331M = 6.3G整个环境一共4G,加上JVM堆内存1.8G(1792M),已经明显的超过了4G。(6.3G + 1.8G)=8.1G > 4G如果按照此计算,应用应用早就被OOM了。怎么回事呢?为了解决这个问题,笔者又思考了好久。如下所示:Java线程底层实现JVM的线程在linux上底层是调用NPTL(Native Posix Thread Library)来创建的,一个JVM线程就对应linux的lwp(轻量级进程,也是进程,只不过共享了mm_struct,用来实现线程),一个thread.start就相当于do_fork了一把。其中,我们在JVM启动时候设置了-Xss=512K(即线程栈大小),这512K中然后有8K是必须使用的,这8K是由进程的内核栈和thread_info公用的,放在两块连续的物理页框上。如下图所示:众所周知,一个进程(包括lwp)包括内核栈和用户栈,内核栈+thread_info用了8K,那么用户态的栈可用内存就是:512K-8K=504K如下图所示:Linux实际物理内存映射事实上linux对物理内存的使用非常的抠门,一开始只是分配了虚拟内存的线性区,并没有分配实际的物理内存,只有推到最后使用的时候才分配具体的物理内存,即所谓的请求调页。如下图所示:查看smaps进程内存使用信息使用如下命令,查看cat /proc/[pid]/smaps > smaps.txt实际物理内存使用信息,如下所示:7fa69a6d1000-7fa69a74f000 rwxp 00000000 00:00 0  Size:                504 kB Rss:                  92 kB Pss:                  92 kB Shared_Clean:          0 kB Shared_Dirty:          0 kB Private_Clean:         0 kB Private_Dirty:        92 kB Referenced:           92 kB Anonymous:            92 kB AnonHugePages:         0 kB Swap:                  0 kB KernelPageSize:        4 kB MMUPageSize:           4 kB 7fa69a7d3000-7fa69a851000 rwxp 00000000 00:00 0  Size:                504 kB Rss:                 152 kB Pss:                 152 kB Shared_Clean:          0 kB Shared_Dirty:          0 kB Private_Clean:         0 kB Private_Dirty:       152 kB Referenced:          152 kB Anonymous:           152 kB AnonHugePages:         0 kB Swap:                  0 kB KernelPageSize:        4 kB MMUPageSize:           4 kB搜索下504KB,正好是12563个,对了12563个线程,其中Rss表示实际物理内存(含共享库)92KB,Pss表示实际物理内存(按比例共享库)92KB(由于没有共享库,所以Rss==Pss),以第一个7fa69a6d1000-7fa69a74f000线性区来看,其映射了92KB的空间,第二个映射了152KB的空间。如下图所示:挑出符合条件(即size是504K)的几十组看了下,基本都在92K-152K之间,再加上内核栈8K(92+152)/2+8K=130K,由于是估算,取整为128K,即反映此应用平均线程栈大小。注意,实际内存有波动的原因是由于环境不同,从而走了不同的分支,导致栈上的增长不同。重新进行内存计算JVM一开始申请了-Xmx1792m -Xms1792m即1.8G的堆内内存,这里是即时分配,一开始就用物理页框填充。12563个线程,每个线程栈平均大小128K,即:128K * 12563=1570M=1.5G的对外内存取个整数128K,就能反映出平均水平。再拿这个128K * 12563 =1570M = 1.5G,加上JVM的1.8G,就已经达到了3.3G,再加上kernel和日志传输进程等使用的内存数量,确实已经接近了4G,这样内存就对应上了!(注:用于定量内存计算的环境是一台内存用量将近4G,但还没OOM的机器)为什么在物理机上没有应用Down机笔者登录了原来物理机,应用还在跑,发现其同样有堆外内存泄露的现象,其物理内存使用已经达到了5个多G!幸好物理机内存很大,而且此应用发布还比较频繁,所以没有被OOM。Dump了物理机上应用的线程,一共有28737个线程,其中28626个线程等待在CachedBnsClient上。同样用smaps查看进程实际内存信息,其平均大小依旧为128K,因为是同一应用的原因继续进行物理内存计算1.8+(28737 * 128k)/1024K =(3.6+1.8)=5.4G进一步验证了我们的推理。这么多线程应用为什么没有卡顿因为基本所有的线程都睡眠在 Thread.sleep(60 * 1000);//一次睡眠60s上。所以仅仅占用了内存,实际占用的CPU时间很少。总结查找Bug的时候,现场信息越多越好,同时定位Bug必须要有实质性的证据。例如内存泄露就要用你推测出的模型进行定量分析。在定量和实际对不上的时候,深挖下去,你会发现不一样的风景!