辽阳剐什教育咨询有限公司

技術(shù)學(xué)院

07-11
2017
基于Maven+SSM整合shiro+Redis實現(xiàn)后臺管理項目
本項目由賣咸魚叔叔開發(fā)完成,歡迎大神指點,慎重抄襲!參考了sojson提供的demo,和官方文檔介紹。完整實現(xiàn)了用戶、角色、權(quán)限CRUD及分頁,還有shiro的登錄認(rèn)證+授權(quán)訪問控制。項目架構(gòu):Maven + SpringMVC + Spring + Mybatis + Shiro + Redis數(shù)據(jù)庫:MySql前端框架:H-ui 首先創(chuàng)建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的最基本的環(huán)境支持依賴,會傳遞依賴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 提供了對其他第三方庫的內(nèi)置支持,如quartz等 -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-context-support</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                        <!-- spring-orm 是spring處理對象關(guān)系映射的組件,傳遞依賴了jdbc、tx等數(shù)據(jù)庫操作有關(guān)的組件 -->            <dependency>                <groupId>org.springframework</groupId>                <artifactId>spring-orm</artifactId>                <version>4.3.2.RELEASE</version>            </dependency>                        <!-- spring-webmvc 是spring處理前端mvc表現(xiàn)層的組件,也即是springMVC,傳遞依賴了web等web操作有關(guān)的組件 -->            <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驅(qū)動包依賴 -->            <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">        <!-- 數(shù)據(jù)庫連接池 -->    <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>        <!-- 事務(wù)管理器 -->    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource" />    </bean>    <!-- 事務(wù)傳播行為 -->    <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>    <!-- 織入事務(wù)增強功能 -->    <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相關(guān)注解支持 -->    <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作為日志實現(xiàn) -->        <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>        <!-- 設(shè)置post請求編碼和響應(yīng)編碼 -->    <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時也對響應(yīng)進(jìn)行編碼 -->            <param-name>forceEncoding</param-name>            <param-value>true</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>characterEncodingFilter</filter-name>        <!-- 設(shè)置為/*時才會攔截所有請求,和servlet有點區(qū)別,servlet設(shè)置為/*只攔截所有的一級請求,如/xx.do,而不攔截/xx/xx.do;servlet設(shè)置為/時才會攔截所有請求 -->        <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.實現(xiàn)用戶、角色、權(quán)限頁面操作功能,這里就不貼代碼了4.接下來就是shiro,登錄認(rèn)證和授權(quán)只需要繼承AuthorizingRealm,其繼承了 AuthenticatingRealm(即身份驗證),而且也間接繼承了 CachingRealm(帶有緩存實現(xiàn))。身份認(rèn)證重寫doGetAuthenticationInfo方法,授權(quán)重寫doGetAuthorizationInfo方法。Shiro 默認(rèn)提供的 Realm 身份認(rèn)證流程 流程如下:首先調(diào)用 Subject.login(token) 進(jìn)行登錄,其會自動委托給 Security Manager,調(diào)用之前必須通過 SecurityUtils.setSecurityManager() 設(shè)置;SecurityManager 負(fù)責(zé)真正的身份驗證邏輯;它會委托給 Authenticator 進(jìn)行身份驗證;Authenticator 才是真正的身份驗證者,Shiro API 中核心的身份認(rèn)證入口點,此處可以自定義插入自己的實現(xiàn);Authenticator 可能會委托給相應(yīng)的 AuthenticationStrategy 進(jìn)行多 Realm 身份驗證,默認(rèn) ModularRealmAuthenticator 會調(diào)用 AuthenticationStrategy 進(jìn)行多 Realm 身份驗證;Authenticator 會把相應(yīng)的 token 傳入 Realm,從 Realm 獲取身份驗證信息,如果沒有返回 / 拋出異常表示身份驗證失敗了。此處可以配置多個 Realm,將按照相應(yīng)的順序及策略進(jìn)行訪問。 代碼實現(xiàn):/** * */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 認(rèn)證 + 授權(quán)   重寫 */public class MyRealm extends AuthorizingRealm {    @Resource    AdminUserService adminUserService;            /* (non-Javadoc)     * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)     */    /**     * 授權(quán)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();        /**根據(jù)用戶ID查詢角色(role),放入到Authorization里.*/        info.setRoles(adminUserService.findRoleByUserId(userId));        /**根據(jù)用戶ID查詢權(quán)限(permission),放入到Authorization里.*/        info.setStringPermissions(adminUserService.findPermissionByUserId(userId));        return info;    }    /* (non-Javadoc)     * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)     */    /**     * 登錄認(rèn)證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("帳號已經(jīng)禁止登錄!");        }        //**密碼加鹽**交給AuthenticatingRealm使用CredentialsMatcher進(jìn)行密碼匹配        return new SimpleAuthenticationInfo(user.getAccount(),user.getPassword(),ByteSource.Util.bytes("3.14159"), getName());    }        /**     * 清空當(dāng)前用戶權(quán)限信息     */    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>              <!--未授權(quán)時跳轉(zhuǎn)的連接  -->        <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>                      <!-- 讀取初始自定義權(quán)限內(nèi)容-->        <!-- 如果使用authc驗證,需重寫實現(xiàn)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>        <!-- 相當(dāng)于調(diào)用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表示瀏覽器關(guān)閉時失效此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>            <!-- 用戶信息記住我功能的相關(guān)配置 -->    <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加密的密鑰 建議每個項目都不一樣 默認(rèn)AES算法 密鑰長度(128 256 512 位)-->        <property name="cipherKey"                  value="#{T(org.apache.shiro.codec.Base64).decode('3AvVhmFLUs0KTA3Kprsdag==')}"/>        <property name="cookie" ref="rememberMeCookie"/>    </bean>        <!-- 記住我功能設(shè)置session的Filter -->    <bean id="RememberMeFilter" class="com.sys.shiro.RememberMeFilter" />        <!-- rememberMeParam請求參數(shù)是 boolean 類型,true 表示 rememberMe -->    <!-- shiro規(guī)定記住我功能最多得user級別的,不能到authc級別.所以如果使用authc,需打開此配置或重寫實現(xiàn)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: 登錄認(rèn)證    * @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("------------------身份認(rèn)證成功-------------------");            resultMap.put("status", 200);            resultMap.put("message", "登錄成功!");        } catch (DisabledAccountException dax) {              logger.info("用戶名為:" + um + " 用戶已經(jīng)被禁用!");            resultMap.put("status", 500);            resultMap.put("message", "帳號已被禁用!");        } catch (ExcessiveAttemptsException eae) {              logger.info("用戶名為:" + um + " 用戶登錄次數(shù)過多,有暴力破解的嫌疑!");            resultMap.put("status", 500);            resultMap.put("message", "登錄次數(shù)過多!");        } catch (AccountException ae) {              logger.info("用戶名為:" + token.getPrincipal() + " 帳號或密碼錯誤!");            String excessiveInfo = ExcessiveAttemptsInfo(um);            if(null!=excessiveInfo){                resultMap.put("status", 500);                resultMap.p
06-17
2017
有關(guān)Servlet和JSP的梳理
大二第一學(xué)期的時候有學(xué)JSP的課,但是因為在開學(xué)之前做過JSP的小項目,所以一個學(xué)期的課也沒聽,直到期末考試成績出來了,才回想JSP的內(nèi)容還有多少記得,沒想到模模糊糊也記不起多少,趕緊回頭學(xué)回來。接下來是關(guān)于Servlet和JSP的梳理。-------------------------------------------------------------------------------------------------------------------------------------------------  Servlet是一個Java程序,一個Servlet應(yīng)用有一個或多個Servlet程序,而且JSP頁面也會被轉(zhuǎn)換和編譯成Servlet程序?! ervlet應(yīng)用無法獨立運行,必須運行在Servlet容器中。Servlet容器將用戶的請求傳遞給Servlet應(yīng)用,并將結(jié)果返回給用戶。由于大部分Servlet應(yīng)用都包含多個JSP頁面,因此更準(zhǔn)確地說是“Servlet/JSP應(yīng)用”?! ∑渲校琒ervlet API是開發(fā)Servlet的主要技術(shù)。而Servlet API有以下4個Java包:Java包包含的內(nèi)容javax.servlet定義Servlet和Servlet容器之間契約的類和接口javax.servlet.http定義HTTP Servlet和Servlet容器之間契約的類和接口javax.servlet.annotation標(biāo)注Servlet、Filter、Listener的標(biāo)注。它還被標(biāo)注原件定義元數(shù)據(jù)javax.servlet.descriptor包含提供程序化登陸web應(yīng)用程序的配置信息的類型?! ervlet技術(shù)的核心是Servlet,它是所有Servlet類必須直接或間接實現(xiàn)的一個接口,而Servlet接口定義了Servlet與Servlet容器之間的契約。這個契約歸結(jié)起來就是,Servlet容器將Servlet類載入內(nèi)存,并在Servlet實例上調(diào)用具體的方法。當(dāng)用戶的請求使得Servlet容器調(diào)用Servlet的Service方法,會傳入一個ServletRequest實例和一個ServletResponse實例,其中,ServletRequest中封裝了當(dāng)前的HTTP請求,而ServletResponse表示當(dāng)前用戶的HTTP響應(yīng)。對于每一個應(yīng)用程序,Servlet容器還會創(chuàng)建一個ServletContext實例,這個對象中封裝了上下文(應(yīng)用程序)的環(huán)境詳情。每個上下文只有一個ServletContext,而且每個Servlet實例都有一個配置的ServletConfig。  Servlet的生命周期方法:  Servlet容器的生命周期方法作用init該方法在Servlet第一次被請求的時候,Servlet就會調(diào)用這個方法,而后不再被調(diào)用,所以可以用這個方法進(jìn)行初始化工作。調(diào)用這個方法時,Servlet容器會傳入一個ServletConfig。Service每當(dāng)請求Servlet時,Servlet容器就會調(diào)用這個方法。第一次請求Servlet時,Servlet容器調(diào)用init方法和Service方法。后續(xù)的請求將只調(diào)用Service方法。destroy當(dāng)要銷毀Servlet時,Servlet容器就會調(diào)用這個方法。一般會在這個方法中編寫清除代碼?! ≡诮榻B一個Servlet中另外兩個非生命周期的方法:Servlet容器的非生命周期方法作用getServletInfo這個方法會返回Servlet的描述getServletConfig這個方法會返回由Servlet容器傳給init方法的ServletConfig。但是為了讓getServletConfig返回一個非null值,必須將傳給init方法的ServletConfig賦給一個類級變量。-------------------------------------------------------------------------------------------------------------------------------------------------   接下來是Servlet的各類接口:  ServletRequest 接口說明ServletRequest對于每一個HTTP請求,Servlet容器都會創(chuàng)建一個ServletRequest實例,并將它傳給Servlet的Service方法。ServletRequest封裝了關(guān)于這個請求的信息。  常用的方法有:    方法說明public int getContentLength()返回請求主體的字節(jié)數(shù)。public java.lang.String getContentType()返回請求主體的MIME類型。public java.lang.String getParameter(java.lang.String name)返回指定請求參數(shù)的值public java.lang.String getProtocol()返回這個HTTP請求的協(xié)議名稱和版本  ServletResponse接口說明ServletResponse該接口表示一個Servlet響應(yīng)。在調(diào)用Servlet的Service方法前,Servlet容器首先創(chuàng)建一個ServletResponse,并將它作為第二個參數(shù)傳給Service方法。ServletResponse隱藏了向瀏覽器發(fā)送響應(yīng)的復(fù)雜過程?! 〕S玫姆椒ǎ骸》椒ㄕf明getWriter()返回了一個可以向客戶端發(fā)送文本的java.io.PrintWriter。默認(rèn)情況下,PrintWriter對象使用ISO-8859-1編碼。setContentType(“type”)設(shè)置響應(yīng)的內(nèi)容類型,并將”text/html”作為一個參數(shù)傳入。如果沒有設(shè)置相應(yīng)內(nèi)容類型,有些瀏覽器就會將HTML標(biāo)簽顯示為普通文本?! ervletConfig  接口說明ServletConfig用于存儲關(guān)于Servlet的配置信息。當(dāng)Servlet容器初始化Servlet時,Servlet容器會給Servlet的init()方法傳入一個ServletConfig。ServletConfig封裝可以通過@WebServlet或描述符傳給Servlet的配置信息?!  〕S玫姆椒ǎ骸》椒ㄕf明java.lang.String getInitParameter(java,lang.String name)為了從Servlet內(nèi)部獲取到初始參數(shù)的值,要在Servlet容器傳給Servlet的init方法的ServletConfig中調(diào)用getInitParameter方法。java.util.Enumration<java.lang.String> getInitParameterNames()返回所有初始參數(shù)名稱的一個Enumeration。String contactName = servletConfig.getInitParameter(“contactName”)獲取contactName參數(shù)值  ServletContext接口說明ServletContext每個Web應(yīng)用程序只有一個上下文,所以ServletContext可以存儲這個上下文,并且ServletContext還可以共享從應(yīng)用程序中的所有資料處訪問到的信息,并且可以動態(tài)注冊Web對象,而且是用ServletContext內(nèi)部的Map保存?! 〕S玫姆椒ǎ骸》椒ㄕf明getServletContext().getInitParameter(String name)獲取在項目下的web.xml中設(shè)置context的初始化參數(shù)this.getServletContext().log(“測試”)在web.xml文件中,使用logger元素來設(shè)置日志文件getAttribute(String name)/get AttributeNames()獲取ServletContext中的屬性setAttribute(String name, Object object)設(shè)置ServletContext中的屬性removeAttribute(String name)移除ServletContext中的屬性  GenericServlet接口說明GenericServletGenericServlet實現(xiàn)了Servlet和ServletConfig接口。將init方法中的ServletConfig賦給一個類級變量,以便可以通過getServletConfig獲取,為Servlet接口中的所有方法提供默認(rèn)的實現(xiàn),而且提供包括ServletConfig中的方法?! ttp Servlets    接口說明HttpServletHttpServlet類覆蓋了javax.servlet.GenericServlet類。使用HttpServlet時,還要借助分別代表Servlet請求和Servlet響應(yīng)的HttpServletRequest和HttpServletResponse對象。而HttpServlet與GenericServlet的差別在于,HttpServlet覆蓋的是doGet或者doPost方法,而不是Service方法,而且使用的是HttpServletRequest和HttpServletResponse,而不是ServletRequest和ServletResponse。HttpServletRequest表示HTTP環(huán)境中的Servlet請求HttpServletResponse表示HTTP環(huán)境中的Servlet響應(yīng)  各個接口常用的方法如下:  接口方法說明 HttpServletRequestjava.lang.String getContextPath()返回表示請求上下文的請求URI部分。 HttpServletRequestCookie[] getCookies()返回一個Cookie對象的數(shù)組。 HttpServletRequestjava.lang.String getHeader(java.lang.String name)返回指定HTTP標(biāo)題的值。 HttpServletRequestjava.lang.String getMethod()返回生成這個請求的HTTP方法名稱 HttpServletRequestjava.lang.String getQueryString()返回請求URL中的查詢字符串。 HttpServletRequestHttpSession getSession()返回與這個請求相關(guān)的會話對象。如果沒有,將創(chuàng)建一個新的會話對象。 HttpServletRequestHttpSession getSession(Boolean create)返回與這個請求相關(guān)的繪畫對象,如果有,并且create參數(shù)為True,將創(chuàng)建一個新的會話對象。 HttpServletResponsevoid addCookie(Cookie cookie)給這個響應(yīng)對象添加一個cookie。 HttpServletResponsevoid addHeader(java.lang.String name, java.lang.String value)給這個響應(yīng)對象添加一個header。 HttpServletResponsevoid sendRedirect(java.lang.String location)發(fā)送一條響應(yīng)碼,將瀏覽器跳轉(zhuǎn)到指定的位置。-------------------------------------------------------------------------------------------------------------------------------------------------  當(dāng)用戶提交HTML表單時,在表單元素中輸入的值就會被當(dāng)作請求參數(shù)發(fā)送到服務(wù)器。HTML輸入域(文本域、隱藏域或者密碼域)或者文本區(qū)的值,會被當(dāng)作字符串發(fā)送到服務(wù)器??盏妮斎胗蚧蛘呶谋緟^(qū)會發(fā)送空的字符串。  包含多個值的select元素發(fā)出一個字符串?dāng)?shù)組,可以通過ServletRequest.getParameterValues進(jìn)行處理?! 『瞬檫^的復(fù)選框會發(fā)送字符串"on"到服務(wù)器,未經(jīng)核查的復(fù)選框則不向服務(wù)器發(fā)送任何內(nèi)容,ServletRequest.getParameter(fieldName)返回null?! 芜x框?qū)⒈贿x中按鈕的值發(fā)送到服務(wù)器。如果沒有選擇任何按鈕,將沒有任何內(nèi)容被發(fā)送到服務(wù)器,并且ServletRequest.getParameter(fieldName)返會null?! ∪绻粋€表單中包含多個輸入同名的元素,那么所有值都會被提交,并且必須利用ServletRequest.getParameterValues來獲取它們。ServletRequest.getParameter將只返回最后一個值。
01-17
2018
WebAPI 實現(xiàn)前后端分離
隨著Web技術(shù)的發(fā)展,現(xiàn)在各種框架,前端的,后端的,數(shù)不勝數(shù)。全棧工程師的壓力越來越大?,F(xiàn)在的前端的框架,既可以做各種Web,又可以做各種APP,前端框架更新?lián)Q代越來越快,越來越多。傳統(tǒng)的模式前端和后端進(jìn)行調(diào)試,修改都非常麻煩。往往前端配合后端很痛苦,后端也嫌前端麻煩。(無解,能動手解決的事,盡量別動嘴。辦公室應(yīng)該常備一些,繃帶,止血條,速效救心丸等藥品。為了阻止事態(tài)升級,辦公室要加強刀具管制條例。)前后端分離前端根據(jù)事先約定好的文檔,可以自己摸擬數(shù)據(jù),然后開發(fā),測試,調(diào)試UI,發(fā)布到線上時把API接口改成線上API接口,即可完事。前端日后增加新功能,修改UI,自己修改,自己編譯更新自己UI站點,發(fā)布線上只要調(diào)上線上API接口即可。并不需要麻煩到后端。兩者工作進(jìn)行分離。后端需要跟前端商量好接口,寫好接口文檔,在接口功能上相互溝通(其實相當(dāng)于需求相互溝通),一旦接口文檔訂好之后,只需按事先約定實現(xiàn)API接口即口。把項目編譯好發(fā)布到線上服務(wù)器。即可完事。后端實現(xiàn)WebApi接口,還可以面對各種調(diào)用,如PC端web,手機(jī)APP,或者其它設(shè)備。一個接口多種調(diào)用,實現(xiàn)代碼去重。工作模式分析對前端和后端進(jìn)行分離。各司其職,各自在自己的領(lǐng)域集中精力研究。更能有效的加深技術(shù)深度。 前后端分離的模式,你需要N名前端工程師和N名后端工程師。首先我們要約定一些返回基本的格式,比如用XML,還是JSON。結(jié)果大多數(shù)前端都是喜歡JSON,因為JS天生就支持JSON。我貼出一些示例代碼  {    "ResultCode": 1300,    "Message":"權(quán)限不足",    "Date":null,   }: ::::返回參數(shù)說明參數(shù)名類型是否必有說明ResultCodeint是返回碼Messagestring是結(jié)果說明DetailErrorjosn否具體錯誤Datejosn否數(shù)據(jù)        ResultCodeResultCode說明1000成功1100服務(wù)器異常1200身份驗證異常1300權(quán)限不足1400傳遞參數(shù)驗證不通過1500版本異常1600業(yè)務(wù)邏輯異常1700系統(tǒng)成升級中1800該接口己棄用          具體異常這是一個有點爭議的地方,有很多業(yè)務(wù)邏輯異常,出于對用戶的友好提示。一些生澀難懂的錯誤提示,直接給到用戶,用戶一臉懵逼。但是后端卻不能修改成友好提示,這樣不方便調(diào)試,尋找問題原因。一般來講,前端可以自動修改友好提示給用戶。如果后端返回字符串,前端寫死在代碼中,萬一,某一天后端認(rèn)為這個描述更符合場景,修改的字符串。敵軍還有30秒到達(dá)戰(zhàn)場。建議:盡量使用異常代碼,大家可以看到上面貼出例子,就使用的異常代碼。每種異常都有唯一編號,描述可以更改。但是編號不變。用戶異常(1601000)說明1601001賬號/密碼錯誤1601002賬號被冰凍1601003原密碼不對    版本控制 每個API都有一個版本,其實也是就針對APP,如果是WEB端的,都是直接升級的因為B/S結(jié)構(gòu)本身就是存在升級方便的優(yōu)勢,只需要把服務(wù)端更新就可以了。版本控制一般用兩種方式第一種:URL不變,版本寫在HTTP標(biāo)頭內(nèi)面。第二種:版本寫在URL上面。本人推薦第二種,比較直接方便了解。示例:http://www.xxx.com/版本號當(dāng)前版本號:v1 http://www.baidu.com/v1/UserSecurity/LoginAPI風(fēng)格現(xiàn)在流行的api風(fēng)格比較多,最出名的就是restful風(fēng)格。按本人的經(jīng)驗,完全走restful風(fēng)格是很困難的,可能也是水平問題,在團(tuán)隊內(nèi)面也要考慮到其它成員的水平問題。我們目前API風(fēng)格還是保留以前風(fēng)格。示例,V*代表版本號http://xx.com/V*/UserSecurity/SignOutHTTP謂詞使用 Post 方法在服務(wù)器上創(chuàng)建/修改/刪除資源使用 Get 方法從服務(wù)器檢索某個資源或者資源集合基本命名規(guī)則使用駱駝式命名法-大駝峰法跨域處理前端站點和后端API布署到不同的站點,就會產(chǎn)生跨域問題。什么是同源策略?同源是域名,協(xié)議,端口相同。也就是說如果不同,則是非同源。同源策略是瀏覽器的一基本的安全功能,非同源訪問,瀏覽器會進(jìn)行拒絕。HMTL上面的SRC地址,你可以指定任何URL,表單提交,你可以提交到任何URL。但是,你如果使用AJAX技術(shù),就會受到同源策略的影響,拒絕提交。現(xiàn)代瀏覽器幾乎都支持跨域資源請求的一種方式。這種技術(shù)叫CORS(跨域資源共享)CORS跨域分兩種第一種,簡單跨域。第二種,復(fù)雜跨域。解決方案:HTTP輸出標(biāo)頭增加如何節(jié)點注意有前端框架版本,對安全要求較高,不能使用通配符*,要指定跨域域名。Access-Control-Allow-Origin:* 下面節(jié)點可填,可不填,根據(jù)實際情況,自行決定。123Access-Control-Allow-Methods:GET,POST,OPTIONSAccess-Control-Allow-Credentials:trueAccess-Control-Allow-Headers:根據(jù)請求頭的內(nèi)容,填寫  注意:復(fù)雜跨域比要簡單跨域麻煩,更花費性能。因為復(fù)雜跨域在請求之前會先發(fā)一個options預(yù)請求,根據(jù)響應(yīng)判斷服務(wù)器是否支持跨域。也就是說,實際上請求了兩次。Cookies作用域不同的站點,如何通用Cookies?一般情況只需把cookies作用域設(shè)置頂級域名,瀏覽器會自動把cookies在訪問子域名的時候捎上去。示例,訪問二級域名時候,cookies默認(rèn)會被傳送過去。頂級域名:baidul.com cookies作用域:.baidu.com 二級域名: www.baidu.com api.baidu.com 示例下面貼一些示例文檔,其它的就不多講啦 基本上,WebApi前后端分離的細(xì)節(jié)和注意點,都記錄下來,還有更多的細(xì)節(jié),需要讀者在開發(fā)過程自己去尋找答案。隨筆完畢!
01-17
2018
我用Python玩小游戲“跳一跳”,瞬間稱霸了朋友圈!
從前幾天微信最新版本 6.6.1 的更新開始,微信小程序游戲“跳一跳”似乎在一夜之間風(fēng)靡了朋友圈。它甚至比五六年前的飛機(jī)大戰(zhàn)游戲都火爆,這種小游戲的火爆不僅僅是因為有魔性、有意思,更重要的是可以進(jìn)行好友 PK!“跳一跳”的小游戲推出后,很多準(zhǔn)備奮發(fā)向上的同學(xué),這個假期的美好愿景被毀了。為了多跳幾步,以及朋友圈的排名,大家在整個假期都是這樣的:就這樣跳啊跳...擠地鐵跳,蹲馬桶跳,乘電梯跳,靜默的每 1 秒都不能浪費在辦公室,還要時刻警覺后面...說好的工作呢...我控制不住我自己??!可是很多人費盡心思跳了一下午也沒超過 100 分但排行榜里四分之三的人都超過三位數(shù)了……真是扎心了……今天小編來告訴你,如何才能獲取高分,如何才能占據(jù)朋友圈榜首?游戲攻略拿高分普通版本的高分秘籍是這樣的:如果你每次都能挑到各自的正中間的話,可以 + 2 分,如果連著跳到中間會 + 4、+6、+8、+10……跳到污水井蓋上面,停留 2 秒,等到下水道聲音響起直接 + 5 分跳到魔方上面,停留 2 秒,等到魔方轉(zhuǎn)正會直接 + 10 分跳到音樂盒上面,停留 2 秒,等到音樂響起會直接 + 30 分跳到便利店,停留 2 秒,等到便利店開門會直接 + 15 分以上是針對普通用戶,但對咱們程序猿來說用這套太 Low 了,接下來要說的是如何從技術(shù)層面去實現(xiàn)高分:技術(shù)手段實現(xiàn)高分通過 Python 手段在 Github 上面已經(jīng)有人用 Python 來玩跳一跳這個游戲了,想多少分就有多少分。GitHub 地址:https://github.com/wangshub/wechat_jump_game步驟:安卓手機(jī)打開 USB 調(diào)試,設(shè)置》開發(fā)者選項》USB 調(diào)試。電腦與手機(jī) USB 線連接,確保執(zhí)行 adb devices 可以找到設(shè)備 id。界面轉(zhuǎn)至微信跳一跳游戲,點擊開始游戲。運行 python wechat_junp_auto.py,如果手機(jī)界面顯示 USB 授權(quán),請點擊確認(rèn)。很有趣!簡單點說就是:用電腦幫你玩微信跳一跳,全自動,不用手動。效果:這里梳理一份稍微完整一點的操作步驟,以 Mac 的為例,Win 的思路是一樣的。另外,這里用的是安卓手機(jī),iOS 也差不多,不過要下載一個 5.5GB 的 Xcode。1、下載程序,打開下面的鏈接,點右側(cè) clone or download,再點 download zip。2、解壓 zip 文檔,再把文件夾挪到桌面,打開文件夾,你會看到很多東東:3、打開 mac 系統(tǒng)自帶的“終端”,這是一個命令行應(yīng)用,win 用 cmd 就可以了吧。4、通過終端進(jìn)入文件夾,命令行如下:~/Decktop/wechat_jump_game-master5、安裝 pip,在終端輸入 sudo easy_install pip 再回車,可能要輸入密碼。6、安裝各種依賴程序,在終端輸入 pip install -r requirements.txt 再回車,系統(tǒng)會自動安裝。requirements.txt 就是文件夾里的一個 txt 文檔,里面寫著會自動安裝哪些程序。pip 就是第 5 步安裝的程序,如果沒安裝,pip install -r requirements.txt 將無法執(zhí)行。7、安裝 adb,打開下面的鏈接查看,有 3 種方法,建議用第二種,是英文,如果你不懂英文可以百度中文教程。https://stackoverflow.com/questions/31374085/installing-adb-on-mac-os-x8、打開安卓手機(jī)的設(shè)置 - 開發(fā)者選項 - USB 調(diào)試(如果沒有開發(fā)者選項,可百度打開開發(fā)者選項的方法),用 USB 線連接手機(jī)和電腦,手機(jī)可能會彈出對話框,點同意。如果出現(xiàn)運行腳本后小人不跳的情況,請檢查是否有打開“USB 調(diào)試(安全模式)”,記得順便打開 USB 模擬點擊。9、在終端輸入 adb devices,如果看到下面這種信息,說明 adb 已正確安裝,也說明電腦成功檢測到手機(jī)。如果你系統(tǒng)是 Win10 或 Win8 可能需要先設(shè)置一下“禁用強制驅(qū)動程序簽名”。不然會出現(xiàn)下面的“文件的哈希值不在指定目錄中”安裝不上 adb 驅(qū)動的問題,網(wǎng)上有教程請自行學(xué)習(xí)。10、打開微信跳一跳點開始,在終端輸入 python wechat_jump_auto.py 點回車,游戲就會自動開始~ 請根據(jù)手機(jī)分辨率運行相應(yīng)的 *.py 文件。注意:我跳了很多次,最后都會掉下盒子,暫時最多只能跳到 1800+ 分,不能一直跳下去。分辨率不同,配置文件也不一樣,具體看 config 這個文件夾。別刷太高分,有人刷到 4000,結(jié)果分?jǐn)?shù)被微信清零。實驗結(jié)果:只要有耐心,你就是王者下面分析一下代碼,Main 部分有一個 While 循環(huán),只要你不終止,它會一直重復(fù)操作。Main部分代碼里面主要調(diào)用的自定義函數(shù)有三個,還有一個 time.sleep 是為了延遲一下:pull_screenshot() #獲取圖像find_piece_and_board(im) #根據(jù)圖像獲取兩個點的坐標(biāo)值jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2))#根據(jù)兩點距離和手機(jī)像素計算按壓時間并 JUMPpull_screenshot()這個函數(shù)主要是利用 adb 來獲取圖像,這里順便說一下“adb”,adb 是連接 Android 手機(jī)與 PC 端的橋梁,可以讓用戶在電腦上對手機(jī)進(jìn)行全面的操作。借助 adb 工具,我們可以管理設(shè)備,還可以進(jìn)行如安裝軟件、系統(tǒng)升級、運行 Shell 命令等等操作。如“pull”就是獲取設(shè)備中的文件,想更多了解 ADB 請自行學(xué)習(xí)~find_piece_and_board()根據(jù)圖像獲取當(dāng)前小人位置和落點的坐標(biāo)系(piece_x, piece_y, board_x, board_y),這個是這個腳本中的核心部分。jump根據(jù)設(shè)定的“長按的時間系數(shù)”計算需要的按壓時間,這個系數(shù)是根據(jù)手機(jī)分辨率推出來的,按壓時間設(shè)定不小于 200ms,核心命令是 adb 的“input swipe”?!癷nput swipe”模擬的是手指在屏幕上的滑動事件,如果兩個點坐標(biāo)不變化就成了長按了。代碼中四個變量的設(shè)置是:“swipe_x1,swipe_y1,swipe_x2,swipe_y2 = 320,410,320,410”,所以是模擬的長按,其實滑動也是可以的。偽造 POST 請求刷分除了可以用 Python 實現(xiàn)高分,還有網(wǎng)友爆料還可以直接偽造 POST 請求刷分,直接改分?jǐn)?shù)。昨日,V2EX 網(wǎng)站上一篇題為《微信跳一跳 可以直接更改分?jǐn)?shù), POST 請求沒有校驗… 》的文章獲得大量曝光,帖中指出微信小程序存在漏洞,跳一跳小游戲可以直接改分?jǐn)?shù)。用戶朱鵬飛根據(jù)帖子的指引,發(fā)現(xiàn)甚至連微信小程序、小游戲的源代碼都可以直接下載,只需要知道 appid 和版本號,就可以直接構(gòu)造 URL 下載后綴為 wxapkg 的源碼包,不需要任何驗證。據(jù)微信公眾號“小專欄平臺”消息,截自 1 月 1 日 23:50,微信官方已經(jīng)修復(fù)了這個漏洞。不過,據(jù)說一些老版本的微信還是可以抓包獲取包地址。最后一個微信已經(jīng)修復(fù) Bug(部分版本沒有修復(fù)),但只要利用好前面兩個攻略,再配合對節(jié)奏的把握,登上朋友圈前幾完全不在話下。話不多說,趕緊去玩吧?。?!
01-17
2018
2018,怎么緩解大數(shù)據(jù)的尷尬
關(guān)于大數(shù)據(jù),最近爆出的一個笑話:在電影業(yè)一次內(nèi)部行業(yè)會議上,一位巨無霸級別的電影業(yè)發(fā)言人說:通過數(shù)據(jù)挖掘,我們發(fā)現(xiàn)不同觀眾的相關(guān)賣品偏好。比如《芳華》的觀眾比《戰(zhàn)狼Ⅱ》觀眾消費了更多的熱飲。這些都是之前我們不知道的,也是無法預(yù)測的。上面這樣一個基于兩部影片的觀影數(shù)據(jù)分析得出來的結(jié)論,看似客觀正確,實則因為模型不完善(缺少觀影季節(jié)的考量)等原因,而鬧出笑話。在近期,我們在給金融科技做盤點的時候,就發(fā)現(xiàn)大數(shù)據(jù)自身就是一個“尷尬”。我們找遍新聞,也沒有發(fā)現(xiàn)這個詞有什么特別值得說道的地方。只能靠著一點時政資料湊齊了這個關(guān)鍵詞的盤點。2017年,大數(shù)據(jù)如此重要,卻又如此沒有料。大數(shù)據(jù)模型不完善,是因為根基不牢大數(shù)據(jù)一直不溫不火,和他的發(fā)展缺陷有很大的關(guān)系。雖然大家極力看好它,但未能迎來行業(yè)的爆發(fā)。和一些做大數(shù)據(jù)的朋友聊天,他們甚至?xí)苤卑椎赝虏圩约杭业臄?shù)據(jù)模型。“那些所謂的數(shù)據(jù)模型之類的鬼東西,你只需瞄上一眼,就能頭疼一整天。模型里的數(shù)據(jù)巨大無比,線索邏輯紛繁復(fù)雜。很多數(shù)據(jù)看似很重要卻極其無聊,對結(jié)果判斷毫無意義,食之無味棄之可惜,雞肋一般的存在?!薄罢f實在的,根本原因不在于技術(shù)的落后,而是整個行業(yè)的發(fā)展根基太淺,無法對數(shù)據(jù)的有效性進(jìn)行勘誤、歸納和合理解釋?!薄按致缘卣f,合理的大數(shù)據(jù)架構(gòu)是,數(shù)據(jù)模型完善,能根據(jù)特定領(lǐng)域做出全面合理的數(shù)據(jù)精簡,去掉無關(guān)數(shù)據(jù)和干擾數(shù)據(jù),梳理出一條合理的客觀建議,并根據(jù)數(shù)據(jù)分析師的主觀判斷和勘誤,再總結(jié)出合理的結(jié)論,對相關(guān)行業(yè)做出準(zhǔn)確的預(yù)判。”“現(xiàn)在呢?本來數(shù)據(jù)模型都存在這樣和那樣的漏洞,卻還想著數(shù)據(jù)處理的完全自動化?!薄岸耆揽靠陀^數(shù)據(jù),完成所謂的人工智能演算,那都是扯淡的事兒?!薄皠偛耪f的那個《芳華》和《戰(zhàn)狼Ⅱ》的笑話其實就是一個看似客觀,實則可笑的分析結(jié)論?!薄斑@是因為,大家一說到大數(shù)據(jù),就太拿數(shù)據(jù)想當(dāng)然了。如果只靠著這點意識去做消費金融領(lǐng)域的數(shù)據(jù)分析,肯定有很多投資人被坑得底兒朝天!”“所以現(xiàn)在掙錢的還是那些靠著倒買倒賣用戶資料的數(shù)據(jù)公司,一個數(shù)據(jù)包,加點水分,到處賣,收益無限?!薄安贿^,最近似乎也沒那么容易整了,因為官方越查越嚴(yán),有些所謂的大數(shù)據(jù)公司搞不動了,怕是要涼了?!蔽锫?lián)網(wǎng)或許是大數(shù)據(jù)公司的真正機(jī)會“除了行業(yè)經(jīng)驗的累積,還需要更多數(shù)據(jù)做線上支撐?!薄爱?dāng)然,并不是說數(shù)據(jù)越多越好,而是說,線上的數(shù)據(jù)越豐富,越有利于我們組織有效數(shù)據(jù)。”“核心問題就在于,如何產(chǎn)生大量的有效數(shù)據(jù)?!薄坝行?shù)據(jù),簡單了說,就某個領(lǐng)域,比如,消費金融領(lǐng)域的某一個小細(xì)分的消費品的相關(guān)數(shù)據(jù),在合理組合和解構(gòu)之后,對行業(yè)發(fā)展做出合理預(yù)判,對投資人預(yù)期負(fù)責(zé)的數(shù)據(jù)。否則,數(shù)據(jù)越大,負(fù)擔(dān)越重,越成不了事兒。”積累經(jīng)驗到什么時候才算是個頭呢?“或許要等到物聯(lián)網(wǎng)時代的真正到來?!睘槭裁?“物聯(lián)網(wǎng)可以讓更多的消費金融數(shù)據(jù)和物流數(shù)據(jù)線上化,個人消費信用信息也將進(jìn)一步線上化,數(shù)據(jù)的歸集和處理將更加高效和全面?!薄安贿^,隨著移動支付的快速發(fā)展,更多人的金融消費能力在線上就基本被呈現(xiàn)了出來,包括個人的消費習(xí)慣和個人征信信息都被線上化,而由此產(chǎn)生的物流信息、住房、貸款信息等都在逐步完成終極線上化,這些對大數(shù)據(jù)來說,都是極好的機(jī)會?!薄按髷?shù)據(jù)行業(yè)機(jī)會很大,但大數(shù)據(jù)是一個不穩(wěn)定的行業(yè),因為一切的數(shù)據(jù)都?xì)w結(jié)到機(jī)器里,而機(jī)器由人來掌控,相關(guān)的操作風(fēng)險完全看自己的風(fēng)險意識和人品。行業(yè)隨時爆發(fā)大規(guī)模風(fēng)險,運氣好只影響數(shù)據(jù)安全,運氣不好,很企業(yè)和個人的信用會破產(chǎn)。這會給行業(yè),甚至整個社會帶來巨大的災(zāi)難?!薄耙虼耍瑥臉I(yè)企業(yè)的相關(guān)準(zhǔn)則需要進(jìn)一步細(xì)化和規(guī)范,對人也需要有個職業(yè)操守方面的管制?!笔裁礃拥娜嗽趺从脭?shù)據(jù),其目的和效果都是不一樣的。這又和一個大數(shù)據(jù)相關(guān)的段子有點關(guān)系,正好段子開頭,笑話結(jié)尾,也還算圓滿。俺家鐘點工說:“俺兒子又被老師訓(xùn)了?!卑硢栍终?她說:學(xué)校請了個政法大學(xué)的教授來給孩子們講課,說還是個名人呢,見天在電視上忽悠。他告誡孩子們不要打架,他說他統(tǒng)計過,打架斗毆死了的人百分之九十五以上都是先動手那個,然后問孩子們這是為什么?俺兒子說因為沒死的說是死了的先動手的。
01-17
2018
解Bug之路:記一次JVM堆外內(nèi)存泄露Bug的查找
前言JVM的堆外內(nèi)存泄露的定位一直是個比較棘手的問題。此次的Bug查找從堆內(nèi)內(nèi)存的泄露反推出堆外內(nèi)存,同時對物理內(nèi)存的使用做了定量的分析,從而實錘了Bug的源頭。筆者將此Bug分析的過程寫成博客,以饗讀者。由于物理內(nèi)存定量分析部分用到了linux kernel虛擬內(nèi)存管理的知識,讀者如果有興趣了解請看ulk3(《深入理解linux內(nèi)核第三版》)內(nèi)存泄露Bug現(xiàn)場一個線上穩(wěn)定運行了三年的系統(tǒng),從物理機(jī)遷移到docker環(huán)境后,運行了一段時間,突然被監(jiān)控系統(tǒng)發(fā)出了某些實例不可用的報警。所幸有負(fù)載均衡,可以自動下掉節(jié)點,如下圖所示:登錄到對應(yīng)機(jī)器上后,發(fā)現(xiàn)由于內(nèi)存占用太大,觸發(fā)OOM,然后被linux系統(tǒng)本身給kill了。應(yīng)急措施緊急在出問題的實例上再次啟動應(yīng)用,啟動后,內(nèi)存占用正常,一切Okay。奇怪現(xiàn)象當(dāng)前設(shè)置的最大堆內(nèi)存是1792M,如下所示:-Xmx1792m -Xms1792m -Xmn900m -XX:PermSi ze=256m -XX:MaxPermSize=256m -server -Xss512k查看操作系統(tǒng)層面的監(jiān)控,發(fā)現(xiàn)內(nèi)存占用情況如下圖所示:上圖藍(lán)色的線表示總的內(nèi)存使用量,發(fā)現(xiàn)一直漲到了4G后,超出了系統(tǒng)限制。很明顯,有堆外內(nèi)存泄露了。查找線索gc日志一般出現(xiàn)內(nèi)存泄露,筆者立馬想到的就是查看當(dāng)時的gc日志。本身應(yīng)用所采用框架會定時打印出對應(yīng)的gc日志,遂查看,發(fā)現(xiàn)gc日志一切正常。對應(yīng)日志如下:查看了當(dāng)天的所有g(shù)c日志,發(fā)現(xiàn)內(nèi)存始終會回落到170M左右,并無明顯的增加。要知道JVM進(jìn)程本身占用的內(nèi)存可是接近4G(加上其它進(jìn)程,例如日志進(jìn)程就已經(jīng)到4G了),進(jìn)一步確認(rèn)是堆外內(nèi)存導(dǎo)致。排查代碼打開線上服務(wù)對應(yīng)對應(yīng)代碼,查了一圈,發(fā)現(xiàn)沒有任何地方顯式利用堆外內(nèi)存,其沒有依賴任何額外的native方法。關(guān)于網(wǎng)絡(luò)IO的代碼也是托管給Tomcat,很明顯,作為一個全世界廣泛流行的Web服務(wù)器,Tomcat不大可能有堆外內(nèi)存泄露。進(jìn)一步查找由于在代碼層面沒有發(fā)現(xiàn)堆外內(nèi)存的痕跡,那就繼續(xù)找些其它的信息,希望能發(fā)現(xiàn)蛛絲馬跡。Dump出JVM的Heap堆由于線上出問題的Server已經(jīng)被kill,還好有其它幾臺,登上去發(fā)現(xiàn)它們也 占用了很大的堆外內(nèi)存,只是還沒有到觸發(fā)OOM的臨界點而已。于是就趕緊用jmap dump了兩臺機(jī)器中應(yīng)用JVM的堆情況,這兩臺留做現(xiàn)場保留不動,然后將其它機(jī)器迅速重啟,以防同時被OOM導(dǎo)致服務(wù)不可用。使用如下命令dump:jmap -dump:format=b,file=heap.bin [pid]使用MAT分析Heap文件挑了一個heap文件進(jìn)行分析,堆的使用情況如下圖所示:一共用了200多M,和之前gc文件打印出來的170M相差不大,遠(yuǎn)遠(yuǎn)沒有到4G的程度。不得不說MAT是個非常好用的工具,它可以提示你可能內(nèi)存泄露的點:這個cachedBnsClient類有12452個實例,占用了整個堆的61.92%。查看了另一個heap文件,發(fā)現(xiàn)也是同樣的情況。這個地方肯定有內(nèi)存泄露,但是也占用了130多M,和4G相差甚遠(yuǎn)。查看對應(yīng)的代碼系統(tǒng)中大部分對于CachedBnsClient的調(diào)用,都是通過注解Autowired的,這部分實例數(shù)很少。唯一頻繁產(chǎn)生此類實例的代碼如下所示:@Override     public void fun() {             BnsClient bnsClient = new CachedBnsClient();           // do something     return  ; }此CachedBnsClient僅僅在方法體內(nèi)使用,并沒有逃逸到外面,再看此類本身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)中,是不可能出現(xiàn)內(nèi)存泄露的。當(dāng)時只粗略的過了一過成員變量,回過頭來細(xì)想,還是漏了不少地方的。更多信息由于代碼排查下來,感覺這塊不應(yīng)該出現(xiàn)內(nèi)存泄露(但是事實確是如此的打臉)。這個類也沒有顯式用到堆外內(nèi)存,而且只占了130M,和4G比起來微不足道,還是先去追查主要矛盾再說。使用jstack dump線程信息現(xiàn)場信息越多,越能找出蛛絲馬跡。先用jstack把線程信息dump下來看下。 這一看,立馬發(fā)現(xiàn)了不同,除了正常的IO線程以及框架本身的一些守護(hù)線程外,竟然還多出來了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方法上面!這些特定線程的數(shù)量正好是12452個,和cachedBnsClient數(shù)量一致!再次check對應(yīng)代碼原來剛才看CachedBnsClient代碼的時候遺漏掉了一個關(guān)鍵的點!    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的構(gòu)造函數(shù),其在里面創(chuàng)建了一個無限循環(huán)的線程,每隔60s啟動一次刷新一下里面的緩存!找到關(guān)鍵點在看到12452個等待在CachedBnsClient.run的業(yè)務(wù)的一瞬間筆者就意識到,肯定是這邊的線程導(dǎo)致對外內(nèi)存泄露了。下面就是根據(jù)線程大小計算其泄露內(nèi)存量是不是確實能夠引起OOM了。發(fā)現(xiàn)內(nèi)存計算對不上由于我們這邊設(shè)置的Xss是512K,即一個線程棧大小是512K,而由于線程共享其它MM單元(線程本地內(nèi)存是是現(xiàn)在線程棧上的),所以實際線程堆外內(nèi)存占用數(shù)量也是512K。進(jìn)行如下計算:12563 * 512K = 6331M = 6.3G整個環(huán)境一共4G,加上JVM堆內(nèi)存1.8G(1792M),已經(jīng)明顯的超過了4G。(6.3G + 1.8G)=8.1G > 4G如果按照此計算,應(yīng)用應(yīng)用早就被OOM了。怎么回事呢?為了解決這個問題,筆者又思考了好久。如下所示:Java線程底層實現(xiàn)JVM的線程在linux上底層是調(diào)用NPTL(Native Posix Thread Library)來創(chuàng)建的,一個JVM線程就對應(yīng)linux的lwp(輕量級進(jìn)程,也是進(jìn)程,只不過共享了mm_struct,用來實現(xiàn)線程),一個thread.start就相當(dāng)于do_fork了一把。其中,我們在JVM啟動時候設(shè)置了-Xss=512K(即線程棧大小),這512K中然后有8K是必須使用的,這8K是由進(jìn)程的內(nèi)核棧和thread_info公用的,放在兩塊連續(xù)的物理頁框上。如下圖所示:眾所周知,一個進(jìn)程(包括lwp)包括內(nèi)核棧和用戶棧,內(nèi)核棧+thread_info用了8K,那么用戶態(tài)的??捎脙?nèi)存就是:512K-8K=504K如下圖所示:Linux實際物理內(nèi)存映射事實上linux對物理內(nèi)存的使用非常的摳門,一開始只是分配了虛擬內(nèi)存的線性區(qū),并沒有分配實際的物理內(nèi)存,只有推到最后使用的時候才分配具體的物理內(nèi)存,即所謂的請求調(diào)頁。如下圖所示:查看smaps進(jìn)程內(nèi)存使用信息使用如下命令,查看cat /proc/[pid]/smaps > smaps.txt實際物理內(nèi)存使用信息,如下所示: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表示實際物理內(nèi)存(含共享庫)92KB,Pss表示實際物理內(nèi)存(按比例共享庫)92KB(由于沒有共享庫,所以Rss==Pss),以第一個7fa69a6d1000-7fa69a74f000線性區(qū)來看,其映射了92KB的空間,第二個映射了152KB的空間。如下圖所示:挑出符合條件(即size是504K)的幾十組看了下,基本都在92K-152K之間,再加上內(nèi)核棧8K(92+152)/2+8K=130K,由于是估算,取整為128K,即反映此應(yīng)用平均線程棧大小。注意,實際內(nèi)存有波動的原因是由于環(huán)境不同,從而走了不同的分支,導(dǎo)致棧上的增長不同。重新進(jìn)行內(nèi)存計算JVM一開始申請了-Xmx1792m -Xms1792m即1.8G的堆內(nèi)內(nèi)存,這里是即時分配,一開始就用物理頁框填充。12563個線程,每個線程棧平均大小128K,即:128K * 12563=1570M=1.5G的對外內(nèi)存取個整數(shù)128K,就能反映出平均水平。再拿這個128K * 12563 =1570M = 1.5G,加上JVM的1.8G,就已經(jīng)達(dá)到了3.3G,再加上kernel和日志傳輸進(jìn)程等使用的內(nèi)存數(shù)量,確實已經(jīng)接近了4G,這樣內(nèi)存就對應(yīng)上了!(注:用于定量內(nèi)存計算的環(huán)境是一臺內(nèi)存用量將近4G,但還沒OOM的機(jī)器)為什么在物理機(jī)上沒有應(yīng)用Down機(jī)筆者登錄了原來物理機(jī),應(yīng)用還在跑,發(fā)現(xiàn)其同樣有堆外內(nèi)存泄露的現(xiàn)象,其物理內(nèi)存使用已經(jīng)達(dá)到了5個多G!幸好物理機(jī)內(nèi)存很大,而且此應(yīng)用發(fā)布還比較頻繁,所以沒有被OOM。Dump了物理機(jī)上應(yīng)用的線程,一共有28737個線程,其中28626個線程等待在CachedBnsClient上。同樣用smaps查看進(jìn)程實際內(nèi)存信息,其平均大小依舊為128K,因為是同一應(yīng)用的原因繼續(xù)進(jìn)行物理內(nèi)存計算1.8+(28737 * 128k)/1024K =(3.6+1.8)=5.4G進(jìn)一步驗證了我們的推理。這么多線程應(yīng)用為什么沒有卡頓因為基本所有的線程都睡眠在 Thread.sleep(60 * 1000);//一次睡眠60s上。所以僅僅占用了內(nèi)存,實際占用的CPU時間很少??偨Y(jié)查找Bug的時候,現(xiàn)場信息越多越好,同時定位Bug必須要有實質(zhì)性的證據(jù)。例如內(nèi)存泄露就要用你推測出的模型進(jìn)行定量分析。在定量和實際對不上的時候,深挖下去,你會發(fā)現(xiàn)不一樣的風(fēng)景!