权限子系统的总结

京淘权限子系统项总结


1.配置文件的设置

1.1 spring-configs.xml (spring的核心配置文件)

 <!-- 自动扫描该包 -->
<context:component-scan base-package="com.jt" />
<!-- 启用mvc注解 -->
<mvc:annotation-driven />
<!-- 定义跳转的文件的前后缀 ,视图模式配置 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- 自动给后面action的方法return的字符串加上前缀和后缀,变成一个 可用的url地址 -->
    <property name="prefix" value="/WEB-INF/pages/" />
    <property name="suffix" value=".html"></property>
</bean>  
<!-- 整合DRUID -->
<util:properties id="cfg" location="classpath:config.properties"/>
<!--配置DruidDataSource连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    destroy-method="close" init-method="init" lazy-init="true">
    <property name="driverClassName" value="#{cfg.driver}" />
    <property name="url" value="#{cfg.url}" />
    <property name="username" value="#{cfg.username}" />
    <property name="password" value="#{cfg.password}" />
</bean>
<!-- 整合mybatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource" />
   <property name="configLocation" value="classpath:mybatis-configs.xml"></property>
   <!-- 自动扫描mapping.xml文件 -->
   <property name="mapperLocations" >
        <list><value>classpath:mapper/*.xml</value></list>
   </property>
</bean>
<!-- 扫描DAO接口所在包,Spring会自动查找其下的dao
              接口,然后为接口创建代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage"
              value="com.jt.**.dao"/>
</bean>

在整合mybatis的时候,可以在spring核心配置文件里面自动扫描mapper.xml文件了,是用SqlSessionFactoryBean的setMapperLocations属性,这样就省去了在mybatis-configs.xml中每一个都要引入新加入的mapper.xml文件了

<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="configLocation" value="classpath:mybatis-configs.xml"></property>
</bean>
像这种方法使用的是configLocation属性,就是引入mybatis的核心配置问价,里面再进行依然mapper.xml文件,但是这样做有一个弊端,就是不能模糊配置,
每一次加入mapper.xml文件都得重新添加

1.2 mybatis-configs.xml(mybatis的核心配置文件)

<configuration>
<settings>
    <!-- mybatis控制台LOG输出 -->
    <setting name="logImpl" value="LOG4J"/>
</settings>
<typeAliases>
    <package name="com.jt.sys.entity"/>
    <package name="com.jt.common.vo"/>
</typeAliases>
</configuration>

这里面要使用log4j进行日志的控制,还需要进行别名的配置


1.3 如果使用注解开发的话,那么就不需要web.xml配置文件了

1.3.1(具体的做法看项目,已经之前的比价)

1.4有一点可以配置上.那就是pom.xml里面配置插件:(配置jdk编译和运行时环境的插件,那么在每一次maven-update的时候,就不需要改Project Facets)

具体的配法:

<build>
    <plugins>    
        <!-- 添加maven编译插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <!-- 编译时环境 -->
                <source>1.7</source>
                <!-- 运行时环境 -->
                <target>1.8</target>
            </configuration>
        </plugin>
</plugins>

2.日期到页面上格式的转换

日期在数据库存的是长整型的数据,但是要显示在页面上,得要是规定的容器格式,这种容器格式的转换有三种方法:
1.第一种方法:
    *在实体类中的的容器属性的get方法上进行日期的转换,因为页面上进行数据的呈现,是通过实体类的get方法进行获取的时间的,方法:
        public String getModifiedTime() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-hh");
            return sdf.format(modifiedTime);
        }

2.第二种方法:
    *是页面利用js来实现:在追加列的时候,使用js的函数实现容器格式的转换.
        "<td>"+new Date(records[i].createdTime).toLocaleDateString()+"</td>" 
    如果忘了这个函数怎么写,可以在浏览器F12进入控制台,进行测试

3.第三种方法:
    *写一个容器转换类:
        /**
         * Spring 整合jackson以后,在将一个对象转换json串时,假如
         * 有日期类型,默认会将日期类型转换为长整型的字符串,当我们
         * 需要一个具体的日期格式字符串时,可以自己写个类继承
         * JsonSerializer然后写serialize方法,在此方法完成
         * 日期对象的转换,具体应用还需要在实体对象的get方法上
         * 借助@JsonSerializer注解使用此类
         */
        public class DateFormatConverter extends JsonSerializer<Date>{
            //private SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
            /**
             * 负责相关对象的转换操作
             * @param value 表示要转换的数据
             * @param gen 负责将转换的数据写到json串中
             */
            @Override
            public void serialize(Date value, 
                    JsonGenerator gen,
                    SerializerProvider serializers)
                    throws IOException, 
                    JsonProcessingException {
                //1.转换数据
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
                String dateStr=sdf.format(value);
                //2.将转换的结果写到json串中
                gen.writeString(dateStr);
            }
        }

这是一个转换类,可以使用注解来实现功能:(这里可以定义一个实体类的父类,里面可以写实体类通用的属性,在里面做日期格式的转换,那么所有的子类的容器格式都转换了)

   public class BaseEntity {

            private Date createdTime;
            private Date modifiedTime;
            private String createdUser;
            private String modifiedUser;
            @JsonSerialize(using=DateFormatConverter.class)
            public Date getCreatedTime() {
                return createdTime;
            }
            public void setCreatedTime(Date createdTime) {
                this.createdTime = createdTime;
            }
            @JsonSerialize(using=DateFormatConverter.class)
            public Date getModifiedTime() {
                return modifiedTime;
            }
            public void setModifiedTime(Date modifiedTime) {
                this.modifiedTime = modifiedTime;
            }
            public String getCreatedUser() {
                return createdUser;
            }
            public void setCreatedUser(String createdUser) {
                this.createdUser = createdUser;
            }
            public String getModifiedUser() {
                return modifiedUser;
            }
            public void setModifiedUser(String modifiedUser) {
                this.modifiedUser = modifiedUser;
            }
    }

###这三种方式,最后一种方式,实现起来方法比较麻烦,但是写好了之后,以后容器格式的转换都可以通用的,不需要再重现写,###
对于SimpleDateFormat来讲又有一个知识点,那就是SimpleDateFormat是线程不安全的,解决办法取折中的办法就是使用ThreadLocal

3.自定义处理的异常

自定义的异常在一开始就要做好,因为只要编写了业务层,就要使用异常处理:

3.1:现在common.exception包中自定义一个异常,这个异常可以继承RuntimeException,生成构造方法,都是继承父类的构造方法:

    /***
     * 为什么使用自定义异常?
     * 更加精确定位具体异常信息
     */

    public class ServiceException  extends RuntimeException {

    private static final long serialVersionUID = -1169100027771948958L;

    public ServiceException() {
        super();
    }

    public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }
    }

3.2定义异常处理类:用来捕获异常,返回给页面:在common.controller包下定义ControllerExceptionHandler类:这是Spring MVC基于注解方式统一实现异常处理的方式:(异常处理完之后的信息是要返回给页面的,所以返回的结果类型是JsonResult)

    @ControllerAdvice
    public class ControllerExceptionHandler {

      @ExceptionHandler(ServiceException.class)
      @ResponseBody
      public JsonResult handleServiceException(
              RuntimeException e){
          System.out.println("handleServiceException");
          e.printStackTrace();
          //封装错误信息
          return new JsonResult(e);
      }
      @ExceptionHandler(AuthorizationException.class)
      @ResponseBody
      public JsonResult handleAuthorizationeException(
              AuthorizationException e){
          System.out.println("handleAuthorizationeException");
          e.printStackTrace();
          //封装错误信息
          return new JsonResult(new ServiceException("没有此权限"));
      }
      //....... 
    }

4.定义一个实体类的父类,里面写父类的所有的共有的属性:BaseEntity

public class BaseEntity {

    private Date createdTime;
    private Date modifiedTime;
    private String createdUser;
    private String modifiedUser;
    @JsonSerialize(using=DateFormatConverter.class)
    public Date getCreatedTime() {
        return createdTime;
    }
    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }
    @JsonSerialize(using=DateFormatConverter.class)
    public Date getModifiedTime() {
        return modifiedTime;
    }
    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }
    public String getCreatedUser() {
        return createdUser;
    }
    public void setCreatedUser(String createdUser) {
        this.createdUser = createdUser;
    }
    public String getModifiedUser() {
        return modifiedUser;
    }
    public void setModifiedUser(String modifiedUser) {
        this.modifiedUser = modifiedUser;
    }
}

这个类的所有跟实现有关的get方法上都添加了@JsonSerialize注解,这样子类就不需要再进行日期的转换,这样处理起来非常方便,并且注意,这个父类是不要实现序列化接口,生成序列号的,让子类来实现就可以了.

5.Shrio框架的应用:(这一步应该放在比较后的位置来做(做笔记时,是为了体系化)

5.1添加相关的依赖:

      <!-- 整合Shiro 安全框架-->

      <dependency>
       <groupId>org.apache.shiro</groupId>
       <artifactId>shiro-spring</artifactId>
       <version>1.3.2</version>
      </dependency>

     <!-- 整合ehcache -->
      <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.3.2</version>
      </dependency>

5.2编写自定义的Realm类: 助此对象访问用户身份信息,权限信息并对 其进行封装,返回给调用者.这个对象先是有认证管理器来调用,然后由授权管理器来调用:

public class ShiroUserRealm extends AuthorizingRealm {

@Autowired
private SysUserDao sysUserDao;

/***
 * 完成授权信息的获取以及封装. 此方法何时调用?(执行授权检测时调用)
 * 
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("realm.doGetAuthorizationInfo");
    // 1.获取登陆用户身份信息
    String username = (String) principals.getPrimaryPrincipal();
    // 2.查找用户的权限信息
    List<String> list = // sys:user:update,sys:user:view,....,
            sysUserDao.findUserPermissions(username);
    System.out.println("list=" + list);
    Set<String> permissions = new HashSet<>();
    for (String permission : list) {
        if (!StringUtils.isEmpty(permission)) {
            permissions.add(permission);
        }
    }
    System.out.println("set=" + permissions);
    // 3.对权限信息进行封装
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.setStringPermissions(permissions);
    return info;
}

/**
 * 完成认证信息的获取以及封装 此方法何时调用?(执行登陆认证时调用)
 * 
 * @param 用于接收用户身份以及凭证信息的对象(用户输入的)
 * 
 * @return AuthenticationInfo 封装了认证信息的对象(从数据库查询到的)
 * 
 *         client-->controller-->service-->realm
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("realm.doGetAuthenticationInfo");
    // 1.获取用户身份信息
    UsernamePasswordToken uToken = (UsernamePasswordToken) token;
    String username = uToken.getUsername();
    // 2.基于用户身份查询数据库信息
    SysUser sysUser = sysUserDao.findUserByUserName(username);
    // 3.对查询结果进行封装.
    // 3.1获取用户salt值,并将其转换为一个字节源对象
    ByteSource byteSource = ByteSource.Util.bytes(sysUser.getSalt());
    // 3.2对用户信息进行封装返回.
    AuthenticationInfo info = new SimpleAuthenticationInfo(sysUser.getUsername(), // 主身份 (可以将用户传过去)
            sysUser.getPassword(), // 已加密的密码
            byteSource, // salt对应的字节源对象
            getName());// realm 的名字
    return info;
}
}

这个来要完成的工作就是用户信息的认证,以及权限的检查,(先走的是认证的方法,然后走的是授权的方法)

授权方法,是要去更加根据用户名字查询当前用户对的权限的.

5.3编写spring的核心配置文件,需要交给spring来管理

<!-- 配置shiro框架-->

<!-- 配置realm对象(将给spring管理) -->
<bean id="userRealm" 
        class="com.jt.sys.service.realm.ShiroUserRealm">
    <!-- 凭证匹配器(密码加密) -->
    <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <property name="hashAlgorithmName" value="MD5"/>
            <!-- <property name="hashIterations" value="1024"/> -->
        </bean>
    </property>
</bean>
<!-- 配置CacheManager对象(不是必须的,主要是为了提高性能,可以对认证信息以及授权信息进行缓存) -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
         will be creaed with a default config:
         <property name="cacheManager" ref="ehCacheManager"/> -->
    <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
         a specific Ehcache configuration to be used, specify that here.  If you don't, a default
         will be used.: -->
    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
</bean>
<!-- 配置securityManager对象(此对象时shiro框架核心) -->
<bean id="securityManager" 
   class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="userRealm"/> 
    <property name="cacheManager" ref="cacheManager"/>  
</bean>
<!-- 配置ShiroFilter(通过此filter的配置实现对请求资源的过滤,哪些请求要放行,哪些要认证)-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
     <!-- shiro的核心安全接口 -->
     <property name="securityManager" ref="securityManager"/>
     <!-- 要求登录时的连接 -->
     <property name="loginUrl" value="/loginUI.do"></property>
     <!-- 登录成功后要跳转的连接(此处已经在登录中处理了) -->
     <!-- <property name="successUrl" value="/index.jsp"></property> -->
     <!-- 访问未对其授权的资源时,要跳转的连接 
     <property name="unauthorizedUrl" value="/default.html"></property>-->
     <!-- shiro连接约束配置 -->
     <property name="filterChainDefinitions">
         <value>
             <!-- 对静态资源设置允许匿名访问 -->
             /bower_components/** = anon
             /build/** = anon
             /dist/** = anon
             /plugins/** = anon
             /doLogin.do = anon
             <!-- 退出 -->
             /doLogout.do = logout  <!-- 会调用Subject的logout方法,此方法会将session清空 -->
             <!-- 剩余其他路径,必须认证通过才可以访问 -->
             /** = authc
         </value>
     </property>
 </bean>

<!--Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" 
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!--启用shiro注解权限检查(@RequestPermissions)-->
<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>

5.4编写web.xml

<!-- 配置Shiro安全过滤器 -->
  <filter>
    <filter-name>DelegatingFilterProxy</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <!-- 初始化参数 -->
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>shiroFilter</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>DelegatingFilterProxy</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这样的配置是拦截所有的路径,一旦有路劲访问过来的,那么就要启用shiroFilter,就要进行拦截,检查

5.5编写登录的Controller和Service:

  @Controller
  @RequestMapping("/")
  public class SysLoginController {
  @Autowired
  private SysUserService sysUserService;
  @RequestMapping("loginUI")
  public String loginUI(){
      return "login";
  }
  @RequestMapping("doLogin")
  @ResponseBody
  public JsonResult doLogin(
        String username,
        String password){
      String r=DigestUtils.md5DigestAsHex("123456".getBytes());
      System.out.println("r="+r);
        sysUserService.login(
                username,
                password);
      return new JsonResult("login ok");
  }
}

@Override
public void login(String username, String password) {
    System.out.println(sysRoleDao.getClass().getName());
    System.out.println("service.login");
    // 0.参数合法性验证
    if (StringUtils.isEmpty(username))
        throw new ServiceException("用户名不能为空");
    if (StringUtils.isEmpty(password))
        throw new ServiceException("密码不能为空");
    // 1.获取Subject(主体)对象
    Subject subject = SecurityUtils.getSubject();
    // 2.封装用户名和密码
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    // 3.执行身份认证
    try {
        subject.login(token);
        // 此请求会提交给SecurityManager
        // SecurityManager会调用认证处理器Authenticator
        // 认证处理器会去访问相关Realm对象获取认证信息
    } catch (AuthenticationException e) {
        e.printStackTrace();
        throw new ServiceException("用户名或密码不正确");
    }
    // 4.记录用户信息
    Session session = SecurityUtils.getSubject().getSession();
    session.setAttribute("user", username);
}

5.5添加权限:

// Subject.isPermitted("sys:user:valid")
// 当访问此方法时,假如有@RequiresPermissions
// 这个注解,表示此用户必须是授权用户,才能访问
@RequiresPermissions("sys:user:valid")
@Override
public int validById(Integer id, Integer valid) {
    // 1.合法性验证
    if (id == null || id < 1)
        throw new ServiceException("数据不合法,id=" + id);
    if (valid == null)
        throw new ServiceException("状态值不能为空");
    if (valid != 0 && valid != 1)
        throw new ServiceException("状态值不正确,valid=" + valid);
    // 2.执行更新操作
    int rows;
    try {
        rows = sysUserDao.validById(id, valid);
    } catch (Throwable e) {
        e.printStackTrace();
        // 报警
        throw new ServiceException("系统维护中");
    }
    // 3.验证结果并处理
    if (rows == 0)
        throw new ServiceException("数据可能已经不存在");
    return rows;
}

方法上加上@RequiredPermissions()注解就可以去检验权限了

6.事务的添加

6.1spring-configs.xml配置文件中进行配置:

<!-- 配置spring事务管理 -->
<!--1)定义事务管理对象(可以将此对象理解为切面) -->
<bean id="txManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2)配置事务策略 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
   <tx:attributes>
       <tx:method name="find*" read-only="true" isolation="READ_COMMITTED"/>
       <tx:method name="save*" propagation="REQUIRED"/>
       <tx:method name="update*" propagation="REQUIRED"/>
       <tx:method name="delete*" propagation="REQUIRED"/>
       <tx:method name="valid*" propagation="REQUIRED"/>
       <tx:method name="*"/>
   </tx:attributes>
</tx:advice>
<!-- 3)配置事务应用(在哪些业务对象的哪些方法上使用事务) -->
<aop:config>
   <aop:pointcut expression="execution(* com.jt.sys.service..*.*(..))"
                 id="txPointcut"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>          
</aop:config>

具体细节看之前的自己以及老师的笔记:


前端业务代码实现的总结:

心得1:start.html是看到的首页,点击左侧的菜单可以跳转到别的页面:分别给这四个菜单绑定点击事件,这里用的是click()函数,因为这个四个菜单都有id,直接用click()函数更加方便一些;

$(function(){
     $("#load-role-id").click(function(){
       $(".container-fluid").load("role/listUI.do",function(){
           $(".container-fluid").removeData();
       });//t=Math.random();
     })
     $("#load-user-id").click(function(){
       $(".container-fluid").load("user/listUI.do",function(){
           $(".container-fluid").removeData();
       });//t=Math.random();
     })
     $("#load-menu-id").click(function(){
       $(".container-fluid").load("menu/listUI.do",function(){
           $(".container-fluid").removeData();
       });//t=Math.random();
     })
  })

click()里面有一个匿名函数,是点击事件需要执行的方法,里面给.container-fluid绑定了一个load()函数,这是异步加载函数,或去找controller,得到返回的页面呈现在.container-fluid上,这个load()函数里面的function()也是一个匿名函数,这是回调函数,指的是异步加载页面之后,需要干的事.这里这个函数的作用是清除数据(可能会出现中途跳转,所以每一次这种大的菜单页面的跳转都得清除.container-fluid上绑定的数据)


心得2:角色页面加载数据:(分页的实现)

function doGetObjects(){
   //获取参数信息
   var pageCurrent=
   $(".pagination").data("pageCurrent");
   if(!pageCurrent)pageCurrent=1;
   var params={"pageCurrent":pageCurrent}
   var name=$("#searchNameId").val();
   if(name)params.name=name;
   //发送异步请求
   $.ajax({
       url:"role/doFindPageObjects.do",
       dataType:"json",
       data:params,
       success:function(result){
         if(result.state==1){
           setTableBodyRows(result.data.records);
           setPagination(result.data)
         }else{
           alert(result.message);
         }
       }
   });
   }

这个函数时去异步加载角色数据,分页调用的也是这个函数,page.html里面js代码的实现是通用的,所以方法名得跟分页调用的方法名一致,这个函数时要先获取当期页(绑定在”.pagination”的data()里面),这个时page.html里面的,因为page.html是加载在role_list.html这个页面中的,所有分页的任何信息都可以获取到,如果当前页为空的话,说明是第一次加载,给一个默认值1,去加载角色信息,只需要传pageCurrent,就行.返回的是一个JsonReslut里面封装了一些状态,或者异常的信息,只要是跟想页面输出数据,在服务端都得返回这个JsonResult对象.

JsonResult是一个值对象(在common.vo包里面)

    /**通过此对象对控制层数据进行封装 
     * 1)正常数据
     * 2)异常数据
     * */
public class JsonResult {
    private static final int SUCCESS=1;
    private static final int ERROR=0;
    /**状态码*/
    private int state=SUCCESS;
    /**状态信息*/
    private String message;
    /**具体数据*/
    private Object data;

    public JsonResult() {
        message="Action OK";
    }
    public JsonResult(Object data){
        this.data=data;
    }
    public JsonResult(Throwable exp){
        this.state=ERROR;
        this.message=exp.getMessage();
    }
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    }    

这个值对象都是通用的,是controller创建返回给页面的,构造方法可以根据需要多定义几个,里面有默认的属性值,因为默认返回的是正确的信息,所有状态码是1

对于角色信息的呈现,需要将所有的信息显示在页面上,所以就得包含每一条角色数据,以及当前页,总页数,总记录数,这也是一个值对象PageObject对象,这个值对象,是由service层封装之后返回给controller层的,

        /***
         * VO: 封装当前页数据(记录信息和分页信息)
         * 
         * @author adminitartor
         * @param <T>
         */
        public class PageObject<T> {
            /** 每页显示的记录 */
            private List<T> records;
            /** 总页数 */
            private int pageCount;
            /** 总记录数 */
            private int rowCount;
            /** 当前页面大小 */
            private int pageSize = 3;
            /** 当前页的页码 */
            private int pageCurrent = 1;

            public List<T> getRecords() {
                return records;
            }

            public void setRecords(List<T> records) {
                this.records = records;
            }

            public int getPageCount() {
                pageCount = rowCount / pageSize;
                if (rowCount % pageSize != 0) {
                    pageCount++;
                }
                return pageCount;
            }

            public void setPageCount(int pageCount) {
                this.pageCount = pageCount;
            }

            public int getRowCount() {
                return rowCount;
            }

            public void setRowCount(int rowCount) {
                this.rowCount = rowCount;
            }

            public int getPageSize() {
                return pageSize;
            }

            public void setPageSize(int pageSize) {
                this.pageSize = pageSize;
            }

            public int getPageCurrent() {
                return pageCurrent;
            }

            public void setPageCurrent(int pageCurrent) {
                this.pageCurrent = pageCurrent;
            }
        }

里面的getPageCount()是需要计算的,不用在service层计算,只需要把总记录数,pageSize()传给这个PageObject对象就行了,这个PageObject被封装在JsonResult里面,页面获取总页数时,会调用getPageCount()的方法,这样就直接在PageObject对象的getPageCount()方法进行计算,因为计算的方法都是一样的


心得3:数据的呈现:

function setTableBodyRows(data){
   //1.获取body对象
   var tBody=$("#tbodyId");
   tBody.empty();
   //2.迭代data,将data添加到Body中
   for(var i in data){
       var tr=$("<tr></tr>");
       //tr对象上绑定一个id值(唯一标识这行记录)
       tr.data("id",data[i].id);
       var tds="<td><input type='checkbox' name='checkItem' value='"+data[i].id+"'></td>"+
       "<td>"+data[i].name+"</td>"+
       "<td>"+data[i].createdTime+"</td>"+
       "<td>"+data[i].modifiedTime+"</td>"+
       "<td><button type='button' class='btn btn-default btn-update'>update</button></td>";
       tr.append(tds);
       tBody.append(tr);
   }
     //每个checkbox上注册一个click事件
   $("tbody input[name='checkItem']").click(doChangeCheckAllState);
   }

这是通过追加的方式来进行数据的呈现,这个函数走到最后,所有的行数据都有了,就得给每一个多选框绑定点击事件了(这也是需要注意的一点:在使用到某个元素,数据,一定要看它是什么时候加载到页面的,)如果使用前还没有加载到页面,那么是没有效果的.

这里有两个小实现:点击全选的时候,下面的多选框都跟着变;当所有的多选框都选中或都未选中时,全选框也要跟着变:
     //执行全选
   $("#checkAll").change(function(){
       doCheckAll($(this).prop("checked"));
   });
执行全选函数的调用是在$(function())页面加载函数里面,因为,全选框是在页面上存在的,不需要去查数据库,把全选框的是否选中数据通过参数传递给
doCheckAll()方法,
---------------------------------------------------------------------------------------------------------------------
//执行全选操作(让列表中的checkbox的状态与表头中checkbox状态一致)
function doCheckAll(state) {//dom
    /*$("tbody input[name='checkItem']")
     .each(function(){
       $(this).prop("checked",state)
     }); */
    $("tbody input[name='checkItem']").prop("checked", state);
}
让所有的checkbox的属性跟着全选框一起改变,有两种实现,这两种实现都可以,第二种直用prop,没有调用each()函数,这也是可以实现的.

心得4:分页的实现
分页的实现是通用的,这个项目的每个页面都要用到分页:

<script type="text/javascript">
   $(".pagination")
   .on("click",".first,.pre,.next,.last",doJumpToPage)
   //设置分页
   function setPagination(data){
   //1.初始化总页数
   $(".pageCount").html("总页数("+data.pageCount+")");
   //2.初始化总记录数
   $(".rowCount").html("总记录数("+data.rowCount+")");
   //3.初始化当前页页码
   $(".pageCurrent").html("当前页("+data.pageCurrent+")");
   //4.绑定数据
   $(".pagination").data("pageCurrent",data.pageCurrent);
   $(".pagination").data("pageCount",data.pageCount);
   }
   function doJumpToPage(){
   //1.获取当前页码以及总页数
   var pageCurrent=
   $(".pagination").data("pageCurrent");
   var pageCount=
   $(".pagination").data("pageCount");
   //2.根据当前点击的对象执行pageCurrent加减操作
   var cls=$(this).prop("class");
   if(cls=="pre"&&pageCurrent>1){
       pageCurrent--;
   }else if(cls=="next"&&pageCurrent<pageCount){
       pageCurrent++;
   }else if(cls=="first"){
       pageCurrent=1;
   }else if(cls=="last"){
       pageCurrent=pageCount;
   }
   //3.保存当前pageCurrent的值
   $(".pagination").data("pageCurrent",pageCurrent);
   //4.按指定页码重新执行查询
   doGetObjects();
   }
</script>

分页页面是要在role_list.html页面加载都已经加载上了,设置分页条上的数据是在一查询到数据之后就立刻调用,是在role_list.html里的getObject的ajax函数里面调用的,

这个分页需要注意的,绑定点击事件的这种鞋发,已经获得当前这个类的类名,进行判断是上一个,还是下一页,还有需要注意的是,点击完之后,还得把pageCurrent作为数据保存在.pagination里面,这个一定要保存,页面pageCurrent在这里会发送改变

具体的一些细节我也写了笔记(前端需要注意的这一跨块)

猜你喜欢

转载自blog.csdn.net/qq_38200548/article/details/80162954