zoukankan      html  css  js  c++  java
  • SpringMVC+Apache Shiro+JPA(hibernate)整合配置

    序:

    关于标题:

      说是教学,实在愧不敢当,但苦与本人文笔有限,实在找不到更合理,谦逊的词语表达,只能先这样定义了。
      其实最真实的想法,只是希望这个关键词能让更多的人浏览到这篇文章,也算是对于自己写文章的一个肯定吧。^_^!

    关于内容:

      再写这系列文章之前,本人和许多人一样都是伸手党,并深深的了解咱伸手党且英文较差的朋友对于新知识的学习及获取中文资料少的痛苦。所以本着“取之于民,共享与民”的原则,记录下实际工作中对SpringMVC+Shiro整合应用的部分心得。本人技术水平有限,仅希望文章对他人有一定的参考价值,足矣。

    关于拍砖:

      请轻拍,很痛的。且最好附上您的高见。

    另:Shiro基础及原理,推荐学习http://kdboy.iteye.com/category/35212,同时感谢他的博客,在我学习Shiro的过程中,给予很大帮助!


    教学:

    一、SpringMVC+Apache Shiro+JPA(hibernate)整合配置

    (1)新建Web工程,且导入所需Jar包。(以下截图为真实项目中删减后保留,如有不需要的JAR包,请自行删除)

    (2)配置web.xml,applicationContext.xml, spring-mvc.xml, log4j.properties

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>
                org.springframework.web.context.ContextLoaderListener
            </listener-class>
        </listener>
    
        <!-- 配置spring管理OpenEntityManagerInViewFilter-->
        <filter>
            <filter-name>hibernateFilter</filter-name>
            <filter-class>
                org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
            </filter-class>
        </filter>
        <filter-mapping>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <!-- Shiro filter -->
        <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>
    
        <!-- 配置Log4j -->
        <context-param>
            <param-name>webAppRootKey</param-name>
            <param-value>spring_springmvc_jpa.root</param-value>
        </context-param>
        <context-param>
            <param-name>log4jConfigLocation</param-name>
            <param-value>classpath:log4j.properties</param-value>
        </context-param>
        <listener>
            <listener-class>
                org.springframework.web.util.Log4jConfigListener
            </listener-class>
        </listener>
    
        <!-- 配置编码过滤器 -->
        <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>
                <param-name>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>characterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <filter>
            <filter-name>HiddenHttpMethodFilter</filter-name>
            <filter-class>
                org.springframework.web.filter.HiddenHttpMethodFilter
            </filter-class>
        </filter>
        <filter-mapping>
            <filter-name>HiddenHttpMethodFilter</filter-name>
            <servlet-name>dispatcherServlet</servlet-name>
        </filter-mapping>
    
        <!-- Spring 刷新Introspector防止内存泄露 -->
        <listener>
            <listener-class>
                org.springframework.web.util.IntrospectorCleanupListener
            </listener-class>
        </listener>
    
    
        <!-- SpringMVC核心分发器 -->
        <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>
                org.springframework.web.servlet.DispatcherServlet
            </servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring-mvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <!-- 覆盖default servlet的/, springmvc servlet将处理原来处理静态资源的映射 -->
        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <jsp-config>
            <jsp-property-group>
                <display-name>JSPConfiguration</display-name>
                <url-pattern>*.jsp</url-pattern>
                <el-ignored>true</el-ignored>
                <page-encoding>utf-8</page-encoding>
                <scripting-invalid>false</scripting-invalid>
            </jsp-property-group>
        </jsp-config>
    
        <welcome-file-list>
            <welcome-file>login.jsp</welcome-file>
        </welcome-file-list>
        
    </web-app>

    applicationContext.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:context="http://www.springframework.org/schema/context"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:cache="http://www.springframework.org/schema/cache"
        xmlns:jaxws="http://cxf.apache.org/jaxws"
        xsi:schemaLocation="
                        http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
                        http://www.springframework.org/schema/context      
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/cache 
                        http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
                        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd ">
        <!-- 注解支持 -->
        <context:annotation-config />
    
        <!-- 启动组件扫描,排除@Controller组件,该组件由SpringMVC配置文件扫描 -->
        <context:component-scan base-package="org.shiro.demo">
            <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Controller" />
        </context:component-scan>
    
        <!-- 属性文件位置 -->
        <context:property-placeholder location="classpath:jdbc.properties" />
    
        <!-- 数据源 -->
        <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
            destroy-method="close">
            <!-- 数据库驱动 -->
            <property name="driverClass" value="${jdbc.driverClassName}" />
            <!-- 相应驱动的jdbcUrl-->
            <property name="jdbcUrl" value="${jdbc.url}" />
            <!-- 数据库的用户名 -->
            <property name="username" value="${jdbc.username}" />
            <!-- 数据库的密码 -->
            <property name="password" value="${jdbc.password}" />
            <!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
            <property name="idleConnectionTestPeriod"
                value="${BoneCP.idleConnectionTestPeriod}" />
            <!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
            <property name="idleMaxAge" value="${BoneCP.idleMaxAge}" />
            <!-- 每个分区最大的连接数 -->
            <property name="maxConnectionsPerPartition"
                value="${BoneCP.maxConnectionsPerPartition}" />
            <!-- 每个分区最小的连接数 -->
            <property name="minConnectionsPerPartition"
                value="${BoneCP.minConnectionsPerPartition}" />
            <!-- 分区数 ,默认值2,最小1,推荐3-4,视应用而定 -->
            <property name="partitionCount"
                value="${BoneCP.partitionCount}" />
            <!-- 每次去拿数据库连接的时候一次性要拿几个,默认值:2 -->
            <property name="acquireIncrement"
                value="${BoneCP.acquireIncrement}" />
            <!-- 缓存prepared statements的大小,默认值:0 -->
            <property name="statementsCacheSize"
                value="${BoneCP.statementsCacheSize}" />
            <!-- 每个分区释放链接助理进程的数量,默认值:3,除非你的一个数据库连接的时间内做了很多工作,不然过多的助理进程会影响你的性能 -->
            <property name="releaseHelperThreads"
                value="${BoneCP.releaseHelperThreads}" />
        </bean>
    
        <!-- JPA实体管理器工厂 -->
        <bean id="entityManagerFactory"
            class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="persistenceProvider" ref="persistenceProvider" />
            <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
            <property name="jpaDialect" ref="jpaDialect" />
    
            <property name="packagesToScan" value="org.shiro.demo.entity" />
    
            <property name="jpaProperties">
                <props>
                    <prop key="hibernate.dialect">
                        org.hibernate.dialect.MySQL5Dialect
                    </prop>
                    <prop key="hibernate.connection.driver_class">
                        com.mysql.jdbc.Driver
                    </prop>
                    <prop key="hibernate.max_fetch_depth">3</prop>
                    <prop key="hibernate.jdbc.fetch_size">18</prop>
                    <prop key="hibernate.jdbc.batch_size">10</prop>
                    <prop key="hibernate.hbm2ddl.auto">update</prop><!-- validate/update/create -->
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.format_sql">false</prop>
                    <prop key="javax.persistence.validation.mode">none</prop>
                </props>
            </property>
        </bean>
        
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">        
            <property name="defaultEncoding" value="utf-8"></property>        
        </bean>
        
        <!-- 用于指定持久化实现厂商类 -->
        <bean id="persistenceProvider"
            class="org.hibernate.ejb.HibernatePersistence" />
            
        <!-- 用于设置JPA实现厂商的特定属性 -->
        <bean id="jpaVendorAdapter"
            class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="database" value="MYSQL" />
        </bean>
        
        <!-- 用于指定一些高级特性 -->
        <bean id="jpaDialect"
            class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    
        <!-- 事务管理器 -->
        <bean id="txManager"
            class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory"
                ref="entityManagerFactory" />
        </bean>
        
        <!-- 注解式事务 -->
        <tx:annotation-driven transaction-manager="txManager" />
        
        <bean id="securityManager"
            class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="shiroDbRealm" />
        </bean>
        
        <!-- 項目自定义的Realm -->
        <bean id="shiroDbRealm" class="org.shiro.demo.service.realm.ShiroDbRealm" ></bean>
        
        <!-- Shiro Filter -->
        <bean id="shiroFilter"
            class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <property name="loginUrl" value="/" />
            <property name="successUrl" value="/system/main" />
            <property name="unauthorizedUrl" value="/system/error" />
            <property name="filterChainDefinitions">
                <value>
                /login = anon
                /validateCode = anon
                /** = authc
                </value>
            </property>
        </bean>
    </beans>

    spring-mvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation=" 
                    http://www.springframework.org/schema/beans 
                    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.1.xsd 
                    http://www.springframework.org/schema/mvc 
                    http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
        
        <mvc:annotation-driven />
    
        <!-- 默认访问跳转到登录页面 -->
        <mvc:view-controller path="/" view-name="forward:/login" />
        
        <context:component-scan base-package="org.shiro.demo.controller" />
        
        <mvc:resources mapping="/resources/**" location="/resources/" />
    
        <!-- 采用SpringMVC自带的JSON转换工具,支持@ResponseBody注解 -->
        <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
            <property name="messageConverters">
                <list>
                    <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
                </list>
            </property>
        </bean>
    
        <!-- 避免IE执行AJAX时,返回JSON出现下载文件 -->
        <bean id="mappingJacksonHttpMessageConverter"
            class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                </list>
            </property>
        </bean>
        
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
            <property name="prefix" value="/" />
            <property name="suffix" value=".jsp" />
        </bean>
        
        <!-- 开启Shiro注解的Spring配置方式的beans。在lifecycleBeanPostProcessor之后运行 -->
        <bean
            class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
            depends-on="lifecycleBeanPostProcessor" />
        <bean
            class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager" />
        </bean>
        
        <bean id="lifecycleBeanPostProcessor"
            class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    
        <!-- shiro为集成spring -->
        <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <property name="exceptionMappings">
                <props>
                    <prop key="org.apache.shiro.authz.UnauthorizedException">/system/error</prop>
                </props>
            </property>
        </bean>
        
    </beans>

    jdbc.properties

    jdbc.driverClassName=com.mysql.jdbc.Driver
    
    jdbc.url=jdbc:mysql://localhost:3306/shirodemo?useUnicode=true&characterEncoding=UTF-8
    
    jdbc.username=root
    
    jdbc.password=root
    
    BoneCP.idleConnectionTestPeriod=60
    
    BoneCP.idleMaxAge=60
    
    BoneCP.maxConnectionsPerPartition=5
    
    BoneCP.minConnectionsPerPartition=1
    
    BoneCP.partitionCount=3
    
    BoneCP.acquireIncrement=2  
    
    BoneCP.statementsCacheSize=0 
      
    BoneCP.releaseHelperThreads=3

    log4j.properties

    log4j.rootLogger=INFO,stdout,file
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n 
    
    log4j.appender.file=org.apache.log4j.DailyRollingFileAppender 
    log4j.appender.file.File=${spring_springmvc_jpa.root}/shirodemo.log
    log4j.appender.file.layout=org.apache.log4j.PatternLayout 
    log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c] [%p] - %m%n
    
    log4j.logger.org.hibernate.tool.hbm2ddl=info

    (3)建立JavaBean,User.java,Role.java,Permisson.java

    User.java

    package org.shiro.demo.entity;
    
    import java.io.Serializable;
    import java.util.Collection;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.Table;
    
    import org.codehaus.jackson.annotate.JsonIgnore;
    
    @Entity
    @Table(name = "CMSUser")
    public class User implements Serializable {
    
        private static final long serialVersionUID = 7419229779731522702L;
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "userid")
        private Long id;
        @Column(length = 50, unique = true)
        private String account;
        @Column(length = 100)
        @JsonIgnore    //springmvc生成json不包含此字段
        private String password;
        @Column(length = 50)
        private String nickname;
        
        @ManyToMany(cascade = {CascadeType.PERSIST })
        @JsonIgnore
        @JoinTable(name = "CMSUserRole", joinColumns = { @JoinColumn(name = "userid", referencedColumnName = "userid") }, 
            inverseJoinColumns = { @JoinColumn(name = "roleid", referencedColumnName = "roleid") })
        private Collection<Role> roles;
        
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getAccount() {
            return account;
        }
    
        public void setAccount(String account) {
            this.account = account;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getNickname() {
            return nickname;
        }
    
        public void setNickname(String nickname) {
            this.nickname = nickname;
        }
    
        public Collection<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(Collection<Role> roles) {
            this.roles = roles;
        }
    
        /**
         * 本函数输出将作为默认的<shiro:principal/>输出.
         */
        public String toString() {
            return account;
        }
    }

    Role.java

    package org.shiro.demo.entity;
    
    import java.io.Serializable;
    import java.util.Collection;
    
    import javax.persistence.Basic;
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "CMSRole")
    public class Role implements Serializable{
    
        private static final long serialVersionUID = 6177417450707400228L;
        
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="roleid")
        private Long id;
        @Column(length=50)
        private String name;
        @Column(length=50)
        private String description;
        
        @ManyToMany(mappedBy = "roles")
        @Basic(fetch = FetchType.LAZY)
        private Collection<User> users;
        
        @ManyToMany(cascade={CascadeType.PERSIST,CascadeType.REFRESH,CascadeType.MERGE}, fetch = FetchType.LAZY)
        @JoinTable(name = "CMSRolePms", 
                joinColumns = { @JoinColumn(name = "roleid", updatable = false) }, 
                inverseJoinColumns = { @JoinColumn(name = "pmsid", updatable = false) })
        private Collection<Permission> pmss;
        
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getDescription() {
            return description;
        }
        public void setDescription(String description) {
            this.description = description;
        }
        public Collection<User> getUsers() {
            return users;
        }
        public void setUsers(Collection<User> users) {
            this.users = users;
        }
        public Collection<Permission> getPmss() {
            return pmss;
        }
        public void setPmss(Collection<Permission> pmss) {
            this.pmss = pmss;
        }
    }

    Permisson.java

    package org.shiro.demo.entity;
    
    import java.io.Serializable;
    import java.util.Collection;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.ManyToMany;
    import javax.persistence.ManyToOne;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;
    
    @Entity
    @Table(name="CMSPermission")
    public class Permission implements Serializable{
    
        private static final long serialVersionUID = -8792590494605747957L;
        
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="pmsid")
        private Long id;
        @Column(length=50)
        private String name;
        @Column(length=100)
        private String description;
        @Column(length=50)
        private String permission;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "parentid")
        private Permission parent;
        
        @OneToMany(mappedBy = "parent",fetch = FetchType.LAZY,cascade={CascadeType.ALL})
        private Collection<Permission> children;
        
        @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "pmss")
        private Collection<Role> roles;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public String getPermission() {
            return permission;
        }
    
        public void setPermission(String permission) {
            this.permission = permission;
        }
    
        public Permission getParent() {
            return parent;
        }
    
        public void setParent(Permission parent) {
            this.parent = parent;
        }
    
        public Collection<Permission> getChildren() {
            return children;
        }
    
        public void setChildren(Collection<Permission> children) {
            this.children = children;
        }
    
        public Collection<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(Collection<Role> roles) {
            this.roles = roles;
        }
    }

    至此,配置基本完成,请继续学习下一篇文章:

     SpringMVC+Apache Shiro+JPA(hibernate)案例教学(二)基于SpringMVC+Shiro的用户登录权限验证 

    转载自:http://www.cnblogs.com/xql4j/archive/2013/03/30/2990841.html

  • 相关阅读:
    Solution: Win 10 和 Ubuntu 16.04 LTS双系统, Win 10 不能从grub启动
    在Ubuntu上如何往fcitx里添加输入法
    LaTeX 笔记---Q&A
    Hong Kong Regional Online Preliminary 2016 C. Classrooms
    Codeforces 711E ZS and The Birthday Paradox
    poj 2342 anniversary party
    poj 1088 滑雪
    poj 2479 maximum sum
    poj 2481 cows
    poj 2352 stars
  • 原文地址:https://www.cnblogs.com/guochunguang/p/3755228.html
Copyright © 2011-2022 走看看