1. 项目目的
本次物流项目目的
- 巩固复习SSM框架的使用
- 学习Shiro安全框架的使用
- 掌握后台权限管理(绝大部分企业后台项目的权限都使用的RBAC权限管理)
- 掌握物流项目的开发的业务以及功能
- 让大家拿到一份需求文档以后,从零开始,如何开发一个完整的企业项目流程
(1) 技术选型
(2) 根据需求文档设计数据库设计
(3) 根据需求文档进行需求分析,完成功能
2. 项目安排
- 完成系统基本的用户权限用户角色功能
- 使用shiro框架完成安全管理
- 完成部分物流业务功能
3. 权限系统
3.1. 什么是RBAC?
名词解释 : 基于角色的权限访问控制(Role-Based Access Control)
任何一个系统的后台都应该有权限管理,公司不同的员工(后台用户)对应的不同的角色,不同的角色就登录系统就拥有不同的权限(访问公司系统对应的界面)
由上面的解释可以得知一个系统访问有三个关键名称
用户,角色,权限
- 用户: 系统后台登录的管理员(张三,李四,王五等等),这个人访问后台系统的账户
- 角色: 一个公司有不同的工作人员,每个人在公司都对应有角色,这些角色下面可以有多个用户(业务员、操作员、财务、仓管员、总经理等角色)
- 权限: 控制系统那些页面(资源)访问需要权限。
3.1.1. 权限,用户,角色之间的关系
一般来讲,在一个系统中,用户并不直接拥有权限,用户拥有对应的角色,再给角色分配对应的权限。 那么,最终通过数据库查询,可以得到,这个用户拥有什么权限
这种设计叫做 RBAC 基于角色的权限访问控制(Role-Based Access Control)
一个角色可以拥有多个用户 ,一个用户也可以有多个权限
一个角色可以多个权限,一个权限也可以有多个权限。
用户角色 : 多对多关系 N-N
角色权限 : 多堆多关系 N-N
|
在实际开发中,一个用户一个角色情况比较多,也就是单角色用户,所以在我们的项目中,我们用户如下设计
一个用户对应一个角色 1-1
一个角色可以用于多个集合 1-n
|
3.2. RBAC权限管理数据库表设计
使用Eclipse的ERMaster插件来设计
用户表
角色表
权限表
3.2.1. ER图
|
3.2.2. Sql
SET SESSION FOREIGN_KEY_CHECKS=0;
/* Drop Tables */
DROP TABLE IF EXISTS permission; DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS role;
/* Create Tables */
-- 权限表 CREATE TABLE permission ( permission_id bigint NOT NULL AUTO_INCREMENT COMMENT '权限id', name varchar(50) COMMENT '权限名称', type varchar(20) COMMENT '权限类型(menu 菜单权限,permission:普通权限)', url varchar(100) COMMENT '权限跳转url地址', expression varchar(50) COMMENT '权限表达式(shiro权限判断使用)', parent_id bigint COMMENT '父权限', sort int COMMENT '菜单权限显示时候排序', PRIMARY KEY (permission_id) ) COMMENT = '权限表';
-- 角色表 CREATE TABLE role ( role_id bigint NOT NULL AUTO_INCREMENT COMMENT '角色id', rolename varchar(50) COMMENT '角色名称', remark varchar(100) COMMENT '角色备注', -- 角色id,多个角色使用逗号隔开 1,2,3,4 permission_ids varchar(200) COMMENT '角色id,多个角色使用逗号隔开 1,2,3,4', PRIMARY KEY (role_id) ) COMMENT = '角色表';
-- 用户表 CREATE TABLE user ( user_id bigint NOT NULL AUTO_INCREMENT COMMENT '用户id', username varchar(50) COMMENT '用户名', realname varchar(50) COMMENT '真实名称', password varchar(100) COMMENT '密码', salt varchar(50) COMMENT '盐(密码加密用)', status int DEFAULT 1 COMMENT '用户状态 1 可用,2,锁定,3离职', create_date date COMMENT '入职日期', role_id bigint NOT NULL COMMENT '角色id', PRIMARY KEY (user_id) ) COMMENT = '用户表';
/* Create Foreign Keys */
ALTER TABLE user ADD FOREIGN KEY (role_id) REFERENCES role (role_id) ON UPDATE RESTRICT ON DELETE RESTRICT ; |
4. 项目环境准备
4.1. 技术选型
整个项目使用的SSM框架 SpringMVC,Spring,MyBatis,MySql数据库,Shiro安全框架
4.2. 使用Maven创建Web项目
|
4.3. 引入maven项目pom文件依赖的和插件配置
Maven的pom文件集成了整个项目所需要的绝大部分框架依赖,后期其他依赖在实际开发中按需引入集合
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.zj</groupId> <artifactId>logistics_system</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <spring.version>4.3.2.RELEASE</spring.version> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.17</log4j.version> <mybatis.version>3.4.4</mybatis.version> <mybatis-spring.version>1.3.2</mybatis-spring.version> <druid.version>1.1.10</druid.version> <shiro.version>1.2.3</shiro.version> <mybatis-pagehelper.version>5.1.8</mybatis-pagehelper.version> <jdbcmysql.version>5.1.26</jdbcmysql.version> <junit.version>4.12</junit.version> <jstl.version>1.2</jstl.version> <jackson.version>2.9.5</jackson.version> <aspect.version>1.7.4</aspect.version> <servlet.version>3.1.0</servlet.version> <jsp.version>2.2.1</jsp.version> </properties> <dependencies> <!-- spring --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspect.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency>
<!-- log4j日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency>
<!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency>
<!-- mybatis的分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>${mybatis-pagehelper.version}</version> </dependency>
<!-- mybatis和spring集成包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring.version}</version> </dependency>
<!-- 加入servlet和jsp的依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency>
<dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>${jsp.version}</version> <scope>provided</scope> </dependency>
<!-- 引入shiro框架的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>${shiro.version}</version> </dependency> <!-- shiro和spring集成包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <!-- MySQL数据库驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${jdbcmysql.version}</version> </dependency>
<!-- 缓存依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.0</version> </dependency>
<!-- jstl标签库 -->
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version> </dependency>
<!-- jackson json转换工具 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> </dependency>
<dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.7</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.7</version> </dependency>
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> <!-- 单元测试jar包 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> </dependencies> <build> <plugins> <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> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <path>/logistics</path> <port>8080</port> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build> </project> |
5. 框架集成配置
整个框架的 Spring,SpringMVC采用 xml方式配置(也可以自定义使用注解配置),
所以需要创建准备对应的xml配置文件
spring.xml | Spring框架的主配置文件,主要 |
springmvc.xml | SpringMVC配置文件 |
db.properties | 数据库连接配置文件 |
log4j.properties | Log4j日志配置文件 |
其他配置文件后面使用到按需配置
使用Web项目,需要将框架位置到web.xml中才能生效
5.1. spring.xml文件配置
Spring.xml配置文件,主要配置spring注解配置包扫描组件位置,MyBatis代理对象创建
事务管理的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd ">
<!-- 配置包扫描位置 --> <context:component-scan base-package="cn.zj.logistics" />
<!-- 读取 db.properties数据库配置文件 --> <context:property-placeholder location="classpath:db.properties" />
<!-- 配置 druid 连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}" /> </bean>
<!-- 配置SqlSessionFactory对象的创建 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入插件 --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"> <property name="properties"> <value> <!-- 方言 --> helperDialect=mysql </value> </property> </bean> </array> </property> <property name="dataSource" ref="dataSource" />
<!-- 配置映射文件 --> <property name="mapperLocations"> <array> <value>classpath:cn/sxt/edu/mapper/*Mapper.xml</value> </array> </property>
<!-- 配置别名,使用包扫描配置 --> <property name="typeAliasesPackage" value="cn.zj.logistics.pojo" />
<!-- 读取mybatis-config.xml文件(使用个性化配置,日志,缓存等等) --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> </bean>
<!-- 使用包扫描的方式创包下面所有接口对应的代理对象 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 配置包扫描创建代理对象的位置 --> <property name="basePackage" value="cn.zj.logistics.mapper" />
<!-- 注意: 需要注入SqlSessionFactory工厂对象的名称 !!! --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
<!-- MyBatis的事务配置 -->
<!-- 配置事务管理器 : what? -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean>
<!-- Spring事务配置 : when? --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- dql操作,一般都是只读事务 --> <tx:method name="get*" read-only="true" propagation="REQUIRED" /> <tx:method name="find*" read-only="true" propagation="REQUIRED" /> <tx:method name="select*" read-only="true" propagation="REQUIRED" /> <tx:method name="query*" read-only="true" propagation="REQUIRED" /> <!-- dml操作,非只读事务 --> <tx:method name="*" read-only="false" /> </tx:attributes> </tx:advice> <!-- 配置AOP切面,将事务切到Service层 --> <aop:config> <!-- 切入点 :where? --> <aop:pointcut expression="execution(* cn.zj.logistics.service.impl.*.*(..))" id="pt" /> <!-- 切面:切入点+通知 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> </aop:config> </beans>
|
5.2. springmvc.xml配置
Springmvc.xml主要配置一些springmvc框架相关配置,开启注解驱动,视图解析器,静态资源处理等等
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd ">
<!-- 开启SpringMVC的注解驱动 --> <mvc:annotation-driven />
<!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 支持静态资源处理 --> <mvc:default-servlet-handler/>
</beans> |
5.3. db.properties配置文件
数据库连接配置文件
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/logistics?characterEncoding=utf-8 jdbc.username=root jdbc.password=root jdbc.maxActive=10 |
5.4. log4j.properties日志配置文件
# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.cn.zj.logistics=TRACE # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n |
5.5. web.xml
Web.xml作为动态web项目的入口文件,spring和springmvc框架集成相关配置需要放到在此文件下配置
<?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" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<!-- 配置spring的授权过滤器,让spring来管理shiro框架 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 配置shirofilter的生命周期交给servlet管理 --> <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>
<!-- 字符编码过滤器 --> <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> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<!-- 1.配置SpringMVC的前端控制器(总控) --> <servlet> <servlet-name>MVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring*.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>MVC</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
<!-- 设置项目的欢迎页面 --> <welcome-file-list> <welcome-file>/login.jsp</welcome-file> </welcome-file-list> </web-app> |
5.6. Springmvc框架集成的测试
5.6.1. 新建一个AdminController
package cn.zj.logistic.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
@Controller @RequestMapping("/admin") public class AdminController {
//后台用户列表显示页面 @RequestMapping("/adminPage.do") public String adminPage() {
return "admin_list"; } } |
5.6.2. adminPage.jsp管理员操作jsp文件
因为springmvc框架配置了视图解析器,所有SpringMVC控制器请求默认都请求转发
到 WEB-INF/view 目录下面
在WEB-INF目录下面新建一个view目录,在下面再创建一个 adminPage.jsp文件
|
5.6.3. 使用maven的tomcat插件启动web项目
|
|
|
5.6.4. 访问页面地址
在页面输入管理员页面地址,出现内容,说明SpringMVC框架集成成功
|
5.7. MyBatis框架集成测试
MyBatis的框架的集成配置在spring.xml文件中已经集成,在此处直接测试即可
5.7.1. 使用MyBatis的逆向工程生成实体类和Mapper映射
实际开发中,绝大部分项目的开发,数据库表的java的domain映射管理,数据库的Mapper文件的生成,都使用的是逆向工程生成的(使用逆向工程开发者不在需要手动去写数据库单表的增删改查操作)
除了逆向工程自动生成,在开发者自己还不太熟悉MyBatis框架的使用情况下,为了学习和巩固MyBatis框架的每个细节使用。还是可以自己完全手动编写代码
5.7.2. 逆向工程配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <context id="context1"> <!-- 注释构建 --> <commentGenerator> <!-- 去掉所有的注释 --> <property name="suppressAllComments" value="true"/> <property name="suppressDate" value="true"/> </commentGenerator>
<!-- 数据库四要素 --> <jdbcConnection connectionURL="jdbc:mysql://localhost:3306/logistics_system" driverClass="org.gjt.mm.mysql.Driver" password="root" userId="root" /> <!-- 实体类 --> <javaModelGenerator targetPackage="cn.zj.logistics.pojo" targetProject="mybatis-generator/src" /> <!-- 映射文件 --> <sqlMapGenerator targetPackage="cn.zj.logistics.mapper" targetProject="mybatis-generator/src" />
<!-- ANNOTATEDMAPPER XMLMAPPER -->
<!-- 操作接口 --> <javaClientGenerator targetPackage="cn.zj.logistics.mapper" targetProject="mybatis-generator/src" type="XMLMAPPER" /> <table tableName="user" domainObjectName="User" enableCountByExample="true" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> <table tableName="role" domainObjectName="Role" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> <table tableName="permission" domainObjectName="Permission" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> </context> </generatorConfiguration> |
5.7.3. 逆向工程操作
|
生成的接口映射文件和实体类
|
5.7.4. 将这两个包里面的文件拷贝到我们项目即可
|
5.7.5. 创建项目service层
因为spring事务管理是切入到service层的,所以我们在测试的时候,先把service层代码写出来,在测试的时候引入service层来测试即可
|
UserService层代码
//Spring的 IOC @Service public class UserServiceImpl implements UserService { //Spring的 DI @Autowired private UserMapper userMapper;
/** * 插入操作 */ @Override public int insert(User record) { return userMapper.insert(record); } /** * 条件查询 */ @Override public List<User> selectByExample(UserExample example) { return userMapper.selectByExample(example); } } |
5.7.6. 编写UserTest单元测试类,测试代码
编写一个测试后台用户的单元测试类型,使用Spring的测试+单元测试来测试一下MyBatis是否集成成功
5.7.6.1. 插入操作
|
5.7.6.2. 查询操作
|
看到上面结果说明 SSM框架集成已经成功了,接下来就可以正式编写我们的项目代码
6. 项目前端模板页面的准备
6.1. jsp页面的准备
|
7. 后台首页访问
7.1. 创建IndexController
后台首页的jsp页面在WEB-INF/view下面,不能直接访问,需要是用户访问一个控制器并请求转发跳转到 index.jsp中的
package cn.zj.logistics.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
@Controller public class IndexController { //后台首页 @RequestMapping("/index.do") public String index() {
//返回的index是逻辑视图页面,自动根据SpringMVC的视图解析器寻找WEB-INF/view下面的 index.jsp return "index"; } //后台首页欢迎页面 @RequestMapping("/welcome.do") public String welcode() { return "welcome"; } }
|
7.2. 后台首页效果
|
到此,后台界面准备完毕,接下来,就是我们具体的RBAC的细节开发了
后台管理员管理
角色管理
权限管理
8. 后台管理员管理
后台管理员管理必须先调整到管理员页面,管理员页面是对应管理员操作的的入口页面,所有操作入门口都在这个页面
- 管理员列表
(1) 分页功能
(2) 条件搜索功能
- 添加管理员
- 删除管理员
- 删除管理员
8.1. 跳转到管理员页面
由于管理员页面 adminpage.jsp 在WEB-INF/view 下面,所以只能访问控制器以后跳转到此页面下面
Index.jsp 菜单页跳转的页面地址
<li><a data-href="${ctx}/admin/adminPage.do" data-title="管理员列表" href="javascript:void(0)">管理员列表</a></li> |
AdminController页面代码
package cn.zj.logistics.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
@Controller @RequestMapping("/admin") public class AdminController {
//后台用户列表显示页面 @RequestMapping("/adminPage.do") public String adminPage() {
return "adminPage"; } } |
8.2. 用户管理界面效果
|
8.3. 管理员列表-功能
8.3.1. 后台查询数据
后台管理员列表的显示,先完成后台的查询操作,再完成前台页面数据的显示,后台查询要考虑到条件搜索,分页功能
在单元测试测试Service层的条件查询方法
|
虽然逆向工程代码可以进行条件查询,但是MyBatis的逆向工程查询功能代码默认是不能进行分页查询的
8.3.2. MyBatis的分页插件 MyBatisHelper
问题 :如何对MyBatis的逆向工程代码进行分页?
解决方案:使用MyBatisHelper分页插件
MyBatisHelper插件是一个业界牛逼人物(刘增辉 )编写的免费的开源的分页插件,是得到MyBatis框架官方推荐的插件,使用起来非常非常非常(重要事情说三遍)简单,一句对代码即可对MyBatis逆向工程代码查询数据进行分页(逼)
MyBatisHelper官方地址
8.3.3. 项目pom文件引入分页插件依赖
最新版本5.18
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.18</version> </dependency> |
插件必须要整合到MyBatis框架的SqlSessionFactory工厂中
在spring和mybatis集成配置文件配置插件信息
<!-- 配置SqlSessionFactory对象的创建 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入插件 --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"> <property name="properties"> <value> <!-- 方言 --> helperDialect=mysql </value> </property> </bean> </array> </property> <property name="dataSource" ref="dataSource" />
<!-- 配置映射文件 --> <property name="mapperLocations"> <array> <value>classpath:cn/sxt/edu/mapper/*Mapper.xml</value> </array> </property>
<!-- 配置别名,使用包扫描配置 --> <property name="typeAliasesPackage" value="cn.sxt.edu.pojo" />
<!-- 读取mybatis-config.xml文件(使用个性化配置,日志,缓存等等) --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> </bean>
|
8.3.4. MyBatisHelper的使用
配置和使用在线地址
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
// 测试查询管理员数据 @Test public void testQuery() throws Exception { //模拟用户前台提交的关键字搜索 String keyword = "乔峰";
/** * 使用PageHelper一个静态方法即可完成分页 * PageHelper.startPage(pageNum, pageSize) * pageNum : 当前页 默认 从1 开始 * pageSize :每页条数 */ //模拟用户提交过来的 页面,和每页的条数 Integer pageNum = 2; Integer pageSize = 10;
//开启分页(注意一定要方在查询代码之前执行) PageHelper.startPage(pageNum,pageSize);
UserExample example = new UserExample(); //条件 Criteria criteria = example.createCriteria(); criteria.andRealnameLike("%"+keyword+"%");
//查询结果(没有分页钱数据) List<User> users = userService.selectByExample(example); /**创建分页对象 *把没有分页查询的数据创建一个分页对象 *此对象封装了分页的所有数据 *1.数据集合 *2.总条数 *3.下一页 *4.每页条数 *5.当前页 *等等等 */ PageInfo<User> pageInfo = new PageInfo<>(users);
System.out.println("每页结果集 :"+pageInfo.getList()); System.out.println("总记录数 :"+pageInfo.getTotal()); System.out.println("当前页 :"+pageInfo.getPageNum()); System.out.println("每页条数 :"+pageInfo.getPageSize()); System.out.println("下一页 :"+pageInfo.getNextPage()); } |
8.3.4.1. 测试结果
注意:分页查询一般会发送两条sql语句,一条是查询对应的总记录数,另外一条是查询对应的结果集(当前页的数据)
DEBUG [main] - ==> Preparing: SELECT count(0) FROM user WHERE (realname LIKE ?) DEBUG [main] - ==> Parameters: %乔峰%(String) TRACE [main] - <== Columns: count(0) TRACE [main] - <== Row: 200 DEBUG [main] - <== Total: 1 DEBUG [main] - ==> Preparing: select user_id, username, realname, password, salt, status, role_id from user WHERE ( realname like ? ) LIMIT ?, ? DEBUG [main] - ==> Parameters: %乔峰%(String), 10(Integer), 10(Integer) TRACE [main] - <== Columns: user_id, username, realname, password, salt, status, role_id TRACE [main] - <== Row: 16, qiaofen10, 乔峰 :10, abc, null, 1, null TRACE [main] - <== Row: 17, qiaofen11, 乔峰 :11, abc, null, 1, null TRACE [main] - <== Row: 18, qiaofen12, 乔峰 :12, abc, null, 1, null TRACE [main] - <== Row: 19, qiaofen13, 乔峰 :13, abc, null, 1, null TRACE [main] - <== Row: 20, qiaofen14, 乔峰 :14, abc, null, 1, null TRACE [main] - <== Row: 21, qiaofen15, 乔峰 :15, abc, null, 1, null TRACE [main] - <== Row: 22, qiaofen16, 乔峰 :16, abc, null, 1, null TRACE [main] - <== Row: 23, qiaofen17, 乔峰 :17, abc, null, 1, null TRACE [main] - <== Row: 24, qiaofen18, 乔峰 :18, abc, null, 1, null TRACE [main] - <== Row: 25, qiaofen19, 乔峰 :19, abc, null, 1, null DEBUG [main] - <== Total: 10 分页对象 :PageInfo{pageNum=2, pageSize=10, size=10, startRow=11, endRow=20, total=200, pages=20, list=Page{count=true, pageNum=2, pageSize=10, startRow=10, endRow=20, total=200, pages=20, reasonable=false, pageSizeZero=false}[cn.zj.logistics.pojo.User@31d0e481, cn.zj.logistics.pojo.User@3243b914, cn.zj.logistics.pojo.User@241e8ea6, cn.zj.logistics.pojo.User@542e560f, cn.zj.logistics.pojo.User@626c44e7, cn.zj.logistics.pojo.User@4dc8caa7, cn.zj.logistics.pojo.User@1d730606, cn.zj.logistics.pojo.User@3bcbb589, cn.zj.logistics.pojo.User@3b00856b, cn.zj.logistics.pojo.User@3016fd5e], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=8, navigateFirstPage=1, navigateLastPage=8, navigatepageNums=[1, 2, 3, 4, 5, 6, 7, 8]} 每页结果集 :Page{count=true, pageNum=2, pageSize=10, startRow=10, endRow=20, total=200, pages=20, reasonable=false, pageSizeZero=false}[cn.zj.logistics.pojo.User@31d0e481, cn.zj.logistics.pojo.User@3243b914, cn.zj.logistics.pojo.User@241e8ea6, cn.zj.logistics.pojo.User@542e560f, cn.zj.logistics.pojo.User@626c44e7, cn.zj.logistics.pojo.User@4dc8caa7, cn.zj.logistics.pojo.User@1d730606, cn.zj.logistics.pojo.User@3bcbb589, cn.zj.logistics.pojo.User@3b00856b, cn.zj.logistics.pojo.User@3016fd5e] 总记录数 :200 当前页 :2 每页条数 :10 下一页 :3
|
8.3.4.2. PageInfo源代码观察
PageInfo封装了所有分页信息数据,
package com.github.pagehelper;
import java.util.Collection; import java.util.List;
/** * 对Page<E>结果进行包装 * <p/> * 新增分页的多项属性,主要参考:http://bbs.csdn.net/topics/360010907 * * @author liuzh/abel533/isea533 * @version 3.3.0 * @since 3.2.2 * 项目地址 : http://git.oschina.net/free/Mybatis_PageHelper */ @SuppressWarnings({"rawtypes", "unchecked"}) public class PageInfo<T> extends PageSerializable<T> { //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 private int size;
//由于startRow和endRow不常用,这里说个具体的用法 //可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号 private int startRow; //当前页面最后一个元素在数据库中的行号 private int endRow; //总页数 private int pages;
//前一页 private int prePage; //下一页 private int nextPage;
//是否为第一页 private boolean isFirstPage = false; //是否为最后一页 private boolean isLastPage = false; //是否有前一页 private boolean hasPreviousPage = false; //是否有下一页 private boolean hasNextPage = false; //导航页码数 private int navigatePages; //所有导航页号 private int[] navigatepageNums; //导航条上的第一页 private int navigateFirstPage; //导航条上的最后一页 private int navigateLastPage;
public PageInfo() { }
/** * 包装Page对象 * * @param list */ public PageInfo(List<T> list) { this(list, 8); } }
|
8.3.4.2.1. PageSerializable源代码
PageSerializable是PageInfo的父类,里面封装了结果集和总条数
package com.github.pagehelper;
import java.io.Serializable; import java.util.List;
/** * @author liuzh */ public class PageSerializable<T> implements Serializable { private static final long serialVersionUID = 1L; //总记录数 protected long total; //结果集 protected List<T> list;
public PageSerializable() { }
public PageSerializable(List<T> list) { this.list = list; if(list instanceof Page){ this.total = ((Page)list).getTotal(); } else { this.total = list.size(); } }
public static <T> PageSerializable<T> of(List<T> list){ return new PageSerializable<T>(list); }
public long getTotal() { return total; }
public void setTotal(long total) { this.total = total; }
public List<T> getList() { return list; }
public void setList(List<T> list) { this.list = list; }
@Override public String toString() { return "PageSerializable{" + "total=" + total + ", list=" + list + '}'; } }
|
8.3.5. 前端页面显示数据
后台页面已经把数据查询出来了,并且也有对应的条件搜索功能,接下来,就是需要把后台数据显示到前断页面 adminPage.jsp上了
前端页面使用ajax发送请求加载用户json数据,返回给前台,使用 BootstrapTables 表格插件显示数据
8.3.6. BootstrapTable插件学习使用
8.3.6.1. 使用前的准备工作
BootstrapTable 是在Bootstrap前端框架基础之上编写的一个数据显示工具,此工具显示基于html 的<table>标签,而且自带分页功能和条件搜索功能。我们开发者使用它后台只需要返回 BootstrapTable 插件所需要的json数据即可,总条数和结果集 (PageHelper 分页插件的分页对象中就包含了 total总条数list 结果集两个数据)。返回后台数据只需要返回把PageInfo对象当做json字符串返回即可
8.3.6.1.1. AdminController 的返回用户数据方法
package cn.zj.logistics.controller;
import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo;
import cn.zj.logistics.pojo.User; import cn.zj.logistics.pojo.UserExample; import cn.zj.logistics.pojo.UserExample.Criteria; import cn.zj.logistics.service.UserService;
@Controller @RequestMapping("/admin") public class AdminController {
@Autowired private UserService userService;
/** * 用户数据列表,前台使用bootstrapTable插件发送ajax请求,返回的是json字符串(前台页面使用的 bootstrapTable插件进行数据显示和分页操作的) * @param keyword 关键字搜索(账号和真实姓名的模糊查询) * @param pageNum 页码(默认第一页) * @param pageSize 每页条数(默认10条数据) * @return */ @RequestMapping("/list.do") @ResponseBody public PageInfo<User> list( String keyword, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { // 开始使用分页插件分页
PageHelper.startPage(pageNum, pageSize);
UserExample example = new UserExample();
if (StringUtils.isNotBlank(keyword)) {
//username账号模糊查询条件 Criteria criteria = example.createCriteria(); criteria.andUsernameLike("%" + keyword + "%"); //真实姓名模糊查询条件 Criteria criteria1 = example.createCriteria(); criteria1.andRealnameLike("%" + keyword + "%"); //或者关系 example.or(criteria1); }
List<User> users = userService.selectByExample(example);
// 创建分页对象 PageInfo<User> pageInfo = new PageInfo<>(users); return pageInfo; }
//后台用户列表显示页面 @RequestMapping("/adminPage.do") public String adminPage() {
return "adminPage"; } }
|
8.3.6.1.2. 访问页面返回的json字符串
|
8.3.6.1.3. 格式化日期
|
8.3.6.1.4. 格式化后效果
|
8.3.7. adminPage.jsp页面开始使用插件
8.3.7.1. 插件下载学习地址
https://bootstrap-table.com/ 插件首页
https://bootstrap-table.com/docs/getting-started/introduction/ 文档学习地址
项目adminPage.jsp引入css样式文件和js文件,前提是项目必须先引入bootstrap前端框架的相关js和css文件,并且必须有jquery文件
|
8.3.7.2. Css样式
<link rel="stylesheet" type="text/css" href="lib/bootstrap-3.3.7-dist/css/bootstrap.css" /> <link rel="stylesheet" type="text/css" href="lib/bootstrap-table/bootstrap-table.css" /> |
8.3.7.3. Js文件
<script type="text/javascript" src="lib/jquery/1.11.3/jquery.min.js"></script> <script type="text/javascript" src="lib/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <script type="text/javascript" src="lib/bootstrap-table/bootstrap-table.js"></script> <script type="text/javascript" src="lib/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script> |
8.3.8. 页面表格table标签
<table id="userTable" class="table table-border table-bordered table-bg"> </table> |
bootstrapTable的具体代码使用
$(function() { $('#userTable').bootstrapTable({ url: '${ctx}/admin/list.do',//ajax请求的url地址 /* ajax请求以后回调函数的处理 后台使用返回的PageInfo对象中的 结果 级的key是list,总条数是total 而前台bootstrapTable插件需要的数据的key叫做rows ,总条数也是叫做total 那么出现一个问题 : 总条数的key能对上,结果集对不上,就需要在ajax请求完成回调 responseHandler 这个函数方法处理一下 并且在自定义一个 json,rows做为key,返回json的 list作为值 total:还是total 这样才能满足 bootstrapTable插件数据的需要 */ responseHandler: function(res) { /* res: 后台分页对象PageInfo返回对应的json对象 res.list : 结果集 res.total : 总记录数 */ var data = {rows: res.list,total: res.total}; return data; }, pagination: true, toolbar: "#toolbar",//顶部显示的工具条(添加和批量删除的) contentType: 'application/x-www-form-urlencoded',//条件搜索的时候ajax请求给后台数据的数据类型(条件搜索post提交必须设置) search: true,//是否显示搜索框 pageNumber: 1,//默认的页面 第一页 pageSize: 10,//默认的每页条数 //pageList:[10,25,50,100],//每页能显示的条数 sidePagination: "server",//是否是服务器分页,每次请求都是对应的10条数据,下一页发送ajax请求 paginationHAlign: 'right', //底部分页条 showToggle: true, //是否显示详细视图和列表视图的切换按钮 cardView: false, //是否显示详细视图 showColumns: true, //是否显示所有的列 showRefresh: true, //是否显示刷新按钮 columns: [ //表格显示数据对应的表头设置, { checkbox: true},//是否显示前台的复选框(多选) /* 每列数据的表头的设置 filed:返回json数据对应数据的key title:表头要显示的名 */ {field: 'userId',title: '编号'}, {field: 'username',title: '账号'}, {field: 'realname',title: '真实名称'}, {field: 'createDate',title: '入职日期'}, //操作列的设置(删除,修改) /* formatter: 格式化这一行,回调一个函数 */ { field:'userId', title:'操作', align:'center', formatter:operationFormatter
}], /*发送请求的参数, params: bootstrapTable的插件内部参数对象包含如下参数 limit, offset, search, sort limit:每页条数 offset:每页的结束位置 search:搜索框对应的值 sort:排序 */ queryParams: function(params) { //此方法在用户分页或者搜索的时候回自动发送ajax请求调用,并把对应的参数传递给后台 return { pageNum: params.offset / params.limit + 1, //页码 pageSize: params.limit, //页面大小 keyword: params.search }; }, })
});
/* 操作行格式化对应的函数 value: 当前列的值 row:当前行的值 index:索引位置 */ function operationFormatter(value,row,index){ console.log(row); var html ='<a title="编辑" href="javascript:;" οnclick="admin_edit('+row.id+')" style="text-decoration:none"><i class="Hui-iconfont"></i></a>'; html += '<a title="删除" href="javascript:;" οnclick="admin_del(this,'+row.id+')" class="ml-5" style="text-decoration:none"><i class="Hui-iconfont"></i></a>'; return html; } |
8.3.9. 效果
|
8.3.10. Ajax请求观察
|
好了,通过上面的代码和各种工具,我们终于完成了后台管理员列表的查询分页以及搜索功能了
8.3.11. 本节总结
总结:在开发中要学会使用工具,插件不要重复造轮子,如果自己写这个操作,能写出来,但是时间太长,以为成本就高,企业注重的是商业效率,不是学术研究
本节掌握新知识点: MyBatis分页插件的使用和bootstaraptable表格分页组件的使用
8.4. 管理员删除
上述管理员列表做完以后,接下来相对简单一些的功能,就是删除功能
点击删除页面的每一行数据的删除按钮,弹出一个提示框,如果用户点击确定删除(删除以后再刷新页面),如果取消,不删除。
|
问题:如何弹出这么漂亮又美丽的提示框呢?
答 :使用弹窗插件 layer.js
8.4.1. Layer弹窗插件
插件官方地址
下载弹窗插件
|
8.4.1.1. 页面引入弹窗插件
<script type="text/javascript" src="${ctx}/lib/layer/2.4/layer.js"></script> |
8.4.1.2. 删除按钮点击触发事件,弹窗
/*管理员-删除*/ function admin_del(userId){ //layer的弹框使用 layer.confirm('确认要删除吗?',function(index){ //如果是点击的确认就会执行函数ajax请求 $.post("${ctx}/admin/delete.do",{"userId":userId},function(data){ //弹出一个提示消息 layer.msg(data.msg, {time: 1000, icon:6});
//如果是1说明删除成功,刷新表格 ,调用 bootstrap插件的刷新一下表格 if(data.code == 1){ refreshTable(); }
}); }); } /** * 刷新表格方法,在删除,修改,添加成功以后调用 */ function refreshTable(){ $("#userTable").bootstrapTable("refresh"); } |
8.4.2. 后台消息对象创建
使用ajax进行dml操作以后,都会给前台返回一个操作状态,成功或者失败,并且返回操作成功或者失败消息,在这里专门封装一个消息对象,用于给前台ajax请求返回的状态判断
package cn.zj.logistics.mo;
/** * 消息对象,此对象主要用于ajax操作以后返回状态 * 如插入数据,修改数据,删除数据以后, * 返回的json对象 */ public class MessageObject { private Integer code; //0 失败 1成功 private String msg;//消息 public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public MessageObject(Integer code, String msg) { super(); this.code = code; this.msg = msg; } @Override public String toString() { return "MessageObject [code=" + code + ", msg=" + msg + "]"; }
} |
8.4.3. 后台AdminController代码
@Controller @RequestMapping("/admin") public class AdminController {
@Autowired private UserService userService;
//根据用户id删除数据:返回 消息对象,对应的json给前端的ajax请求 @RequestMapping("/delete.do") @ResponseBody public MessageObject delete(Long userId) { //调用业务层删除方法 int row = userService.deleteByPrimaryKey(userId);
MessageObject mo = null; if(row == 1) { mo = new MessageObject(1, "删除管理员成功"); }else { mo = new MessageObject(0, "删除管理员失败"); } return mo; } |
8.4.4. 删除效果
删除前
|
删除中
|
删除后瞬间
|
8.5. 管理员添加
完成删除删除管理员以后,接下载做添加操作
添加思路
- 点击添加按钮弹出一个模态框,模态框中显示添加管理对应的表单
- 在提交数据之前要校验表单合法性,账号在数据库中是否存在账号(账号是唯一的)
- 使用ajax提交数据
- 提交数据成功以后关闭模态框,并且父页面的表格bootstraptable 刷新
8.5.1. 添加用户管理员页面准备
点击页面的添加按钮弹出跳转到adminEdit.jsp页面,因为adminEdit.jsp页面在WEB-INF/view下面所以,用户发送请求先去控制器,再请求转发跳转到 adminEdit.jsp
使用 layer 弹窗插件模态方式弹出页面
8.5.1.1. 弹框代码
//弹出添加管理员页面方法 function adminAdd(){ //使用layer的弹框模态显示 layer.open({ type: 2, title: '添加管理员', shadeClose: true, shade: false, maxmin: true, //开启最大化最小化按钮 area: ['800px', '400px'], content: '${ctx}/admin/edit.do' }); } |
8.5.1.2. 后台AdminController代码
@Controller @RequestMapping("/admin") public class AdminController {
@Autowired private UserService userService; //引入角色Service,在添加和修改的时候页面显示所有的角色信息 @Autowired private RoleService roleService;
// 根据用户id删除数据:返回 消息对象,对应的json给前端的ajax请求 @RequestMapping("/edit.do") public String edit(Model m) {
//查询出所有的角色,并共享到用户编辑页面 RoleExample example = new RoleExample(); List<Role> roles = roleService.selectByExample(example); //共享角色信息 m.addAttribute("roles", roles);
return "adminEdit"; } } |
8.5.1.3. 显示效果
|
8.5.2. 添加管理员表单提交与验证
添加页面准备好了以后,接下来就是用户提交表单和提交前的表单校验了
8.5.2.1. 表单验证-jquery.validate插件
在提交表单之前肯定要做一定的校验的,如用户信息不能为空,账号最少几位,数据库中是否存在等等。
我们使用一个专业的强大表单校验插件,基于Jquery编写的
Jquery.validate 表单校验插件
https://jqueryvalidation.org/ 官网
https://www.runoob.com/jquery/jquery-plugin-validate.html 在线学习地址
8.5.2.2. 引入js代码
<script type="text/javascript" src="${ctx}/lib/jquery.validation/1.14.0/jquery.validate.js"></script> <script type="text/javascript" src="${ctx}/lib/jquery.validation/1.14.0/validate-methods.js"></script> <script type="text/javascript" src="${ctx}/lib/jquery.validation/1.14.0/messages_zh.js"></script> |
8.5.2.3. 校验操作
正常一个表单点击提交submit按钮会提交,如果把这个表单交给jquery.validate以后此表单就不会提交了,而是校验成功以后开发者需要自己重新编写提交表单数据操作
基本实例规则如下
/* $("#表单id").validate({ rules(规则):{ 表单元素名称1(name的值):{ 规则1:值, 规则2:值
}, 表单元素名称2(name的值):{ 规则1:值, 规则2:值
} }, messages(错误消息):{ 表单元素名称1(name的值):{ 规则1:错误提示信息, 规则2:错误提示信息
}, 表单元素名称2(name的值):{ 规则1:错误提示信息, 规则2:错误提示信息
} }, //表单校验成功以后执行方法 submitHandler:function(form){ //表单校验成功以后可以发送ajax请求提交数据到后台了 }
}) */ |
前台js校验代码
//开启校验 $("#userForm").validate({ rules:{//校验规则 username:{//账号 required:true,//不能为空 minlength:4,//最小4位数 maxlength:12,//最大12位数 remote: {//使用ajax接受返回的是boolean类型true可用,false不可用 url: "${ctx}/admin/checkUsername.do", //后台处理程序 type: "post", //数据发送方式 dataType: "json", //接受数据格式 data: { //要传递的数据 username: function() { return $("#username").val(); } } } }, realname:{//真实名称 required:true//不能为空 }, password:{//密码 required:true,//不能为空 }, password2:{//确认密码 required:true,//不能为空 equalTo: "#password"//确认必须和密码相等 }, roleId:{//角色 min:1,//最小必须为1 }, }, messages:{//校验失败的提示小实习 username:{//账号 required:"账号不能为空",//不能为空 minlength:"账号最少4位数",//最小4位数 maxlength:"账号最大12位数",//最大12位数 remote:"账号已经存在请换一个账号" }, realname:{//真实名称 required:"真实姓名不能为空"//不能为空 }, password:{//密码 required:"密码不能为空",//不能为空 }, password2:{//确认密码 required:"确认密码不能为空",//不能为空 equalTo: "确认密码必须和密码相同"//确认必须和密码相等 }, roleId:{//角色 min:"请选择一个角色" }, }, //校验成功以后知悉的回调函数 form参数 表单 submitHandler:function(form){ console.log(form); } }); |
后台服务器检查用户是否存在代码
@Controller @RequestMapping("/admin") public class AdminController {
@Autowired private UserService userService; //引入角色Service,在添加和修改的时候页面显示所有的角色信息 @Autowired private RoleService roleService;
//jquery.validate 插件的ajax请求检查用户名是否存在 @RequestMapping("/checkUsername.do") @ResponseBody public boolean checkUsername(String username) {
UserExample example = new UserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<User> users = userService.selectByExample(example ); if(users.size() == 1) { //返回false说明账号存在不能使用 return false; }else { //账号不存在可以使用 return true; } } } |
校验效果
|
8.5.3. 使用ajax提交表单数据
在jquery.validate 的submitHandler 回调函数中提交开发者自己提交表单数据到后台进行插入操作
$(function(){ //开启校验 $("#userForm").validate({ rules:{//校验规则 username:{//账号 required:true,//不能为空 minlength:4,//最小4位数 maxlength:12,//最大12位数 remote: {//使用ajax接受返回的是boolean类型 url: "${ctx}/admin/checkUsername.do", //后台处理程序 type: "post", //数据发送方式 dataType: "json", //接受数据格式 data: { //要传递的数据 username: function() { return $("#username").val(); } } } }, realname:{//真实名称 required:true//不能为空 }, password:{//密码 required:true,//不能为空 }, password2:{//确认密码 required:true,//不能为空 equalTo: "#password"//确认必须和密码相等 }, roleId:{//角色 min:1,//最小必须为1 }, }, messages:{//校验失败的提示小实习 username:{//账号 required:"账号不能为空",//不能为空 minlength:"账号最少4位数",//最小4位数 maxlength:"账号最大12位数",//最大12位数 remote:"账号已经存在请换一个账号" }, realname:{//真实名称 required:"真实姓名不能为空"//不能为空 }, password:{//密码 required:"密码不能为空",//不能为空 }, password2:{//确认密码 required:"确认密码不能为空",//不能为空 equalTo: "确认密码必须和密码相同"//确认必须和密码相等 }, roleId:{//角色 min:"请选择一个角色" }, }, //校验成功以后知悉的回调函数 form参数 表单 submitHandler:function(form){ //设置表单的action提交地址 form.action = "${ctx}/admin/insert.do";
//调用表单的ajax提交方式 $(form).ajaxSubmit(function(data){ //如果是1说明添加成功,刷新表格 ,调用 bootstrap插件的刷新一下表格 if(data.code == 1){ layer.msg(data.msg,{icon:1,time:2000},function(){ //获取当前弹出层索引 var index = parent.layer.getFrameIndex(window.name); //让父层页面重新刷新一下(重新加载一下) window.parent.refreshTable(); //关闭当前弹出层 parent.layer.close(index); });
}
}); } }); }); |
8.5.4. 后台插入代码
@Controller @RequestMapping("/admin") public class AdminController {
@Autowired private UserService userService; // 引入角色Service,在添加和修改的时候页面显示所有的角色信息 @Autowired private RoleService roleService;
// 新增后台管理员方法 @RequestMapping("/insert.do") @ResponseBody public MessageObject insert(User user) { // 调用业务层删除方法 int row = userService.insert(user);
MessageObject mo = null; if (row == 1) { mo = new MessageObject(1, "添加管理员成功"); } else { mo = new MessageObject(0, "添加管理员失败"); } return mo; } } |
8.6 管理员修改
修改和添加页面差不多,逻辑也是一样,界面使用同一个界面,只是最终提交表单的时候是走的修改逻辑,页面需要数据回显
修改思路
- 点击添加按钮弹出一个模态框,模态框中显示添加管理对应的表单
- 在提交数据之前要校验表单合法性,账号不能修改,只能修改其他信息
- 使用ajax提交数据
- 提交数据成功以后关闭模态框,并且父页面的表格bootstraptable 刷新
8.6.1. 点击修改弹出模态并回显数据
8.6.1.1. 前台js代码
/** * 修改用户 userId:用户id,查询对应的用户数据用于回显 */ function adminEdit(userId){ //使用layer的弹框模态显示 layer.open({ type: 2, title: '添加管理员', shadeClose: true, shade: false, maxmin: true, //开启最大化最小化按钮 area: ['800px', '400px'], content: '${ctx}/admin/edit.do?userId='+userId }); }
|
8.6.1.2. 后台代码
添加和修改走的是一个页面跳转逻辑,但是修改的时候需要接受userId查询出来并回显
//编辑 @RequestMapping("/edit.do") public String edit(Long userId,Model m) { System.out.println("userId:"+userId); //如果userId 不等于空,说明是编辑操作,需要查询出对应用户id的信息并且回显过去 if(userId !=null) { //根据用户id查询用户信息 User user = userService.selectByPrimaryKey(userId); //共享用户信息 m.addAttribute("user", user); }
// 查询出所有的角色,并共享到用户编辑页面 RoleExample example = new RoleExample(); List<Role> roles = roleService.selectByExample(example); // 共享角色信息 m.addAttribute("roles", roles);
return "adminEdit"; } |
8.6.2. 校验并提交数据
注意:因为新增和修改走的是同一个校验逻辑,那么表单校验代码是一样的,但是如何区分什么时候是添加什么时候是修改呢,因为修改的时候不需要校验账号的,此时 jsp的标签库就起到作用了,因为jsp的jstl标签在页面的任何地方都可以使用,包括在js中一样使用,我们在修改的时候,后台会把对应的 user对象共享过来,我们只需要用 jstl+el表达式判断有没有user对象,有user对象走修改操作,没有就走新增操作
//开启校验 $("#userForm").validate({ rules:{//校验规则 <c:if test="${empty user}"> username:{//账号 required:true,//不能为空 minlength:4,//最小4位数 maxlength:12,//最大12位数 remote: {//使用ajax接受返回的是boolean类型 url: "${ctx}/admin/checkUsername.do", //后台处理程序 type: "post", //数据发送方式 dataType: "json", //接受数据格式 data: { //要传递的数据 username: function() { return $("#username").val(); } } } }, </c:if> realname:{//真实名称 required:true//不能为空 }, password:{//密码 required:true,//不能为空 }, password2:{//确认密码 required:true,//不能为空 equalTo: "#password"//确认必须和密码相等 }, roleId:{//角色 min:1,//最小必须为1 }, }, messages:{//校验失败的提示消息 <c:if test='${empty user}'> username:{//账号 required:"账号不能为空",//不能为空 minlength:"账号最少4位数",//最小4位数 maxlength:"账号最大12位数",//最大12位数 remote:"账号已经存在请换一个账号" }, </c:if> realname:{//真实名称 required:"真实姓名不能为空"//不能为空 }, password:{//密码 required:"密码不能为空",//不能为空 }, password2:{//确认密码 required:"确认密码不能为空",//不能为空 equalTo: "确认密码必须和密码相同"//确认必须和密码相等 }, roleId:{//角色 min:"请选择一个角色" }, }, //校验成功以后知悉的回调函数 form参数 表单 submitHandler:function(form){ //设置表单的action提交地址:如果添加走 insert <c:if test='${user == null}'> form.action = "${ctx}/admin/insert.do"; </c:if>
//设置表单的action提交地址:如果修改走 update <c:if test='${user != null}'> form.action = "${ctx}/admin/update.do"; </c:if>
//调用表单的ajax提交方式 $(form).ajaxSubmit(function(data){ //如果是1说明添加成功,刷新表格 ,调用 bootstrap插件的刷新一下表格 if(data.code == 1){ layer.msg(data.msg,{icon:1,time:2000},function(){ //获取当前弹出层索引 var index = parent.layer.getFrameIndex(window.name); //让父层页面重新刷新一下(重新加载一下) window.parent.refreshTable(); //关闭当前弹出层 parent.layer.close(index); });
}
}); } }); |
8.6.3. 后台修改代码
@Controller @RequestMapping("/admin") public class AdminController {
@Autowired private UserService userService; // 引入角色Service,在添加和修改的时候页面显示所有的角色信息 @Autowired private RoleService roleService;
// 修改后台管理员方法 @RequestMapping("/update.do") @ResponseBody public MessageObject update(User user) { // 调用业务层删除方法 int row = userService.updateByPrimaryKeySelective(user);
MessageObject mo = null; if (row == 1) { mo = new MessageObject(1, "修改管理员成功"); } else { mo = new MessageObject(0, "修改管理员失败"); } return mo; } } |
8.6.4. 效果
|
8.7. 总结
在用户管理这个功能里面,需要掌握一些前端相关的插件
MyBatisHelper 后台MyBatis分页插件
bootstraptable 表格插件,
Jquery.validate 表单校验插件,
layer 弹框插件
有了这些插件,可以快速的完成页面的数据操作,当然所有的这些操作开发者可以完全自己开发,但是我们不需要重复造轮子