-----------------------------------------------------------------------------------------------------------------
前置要求:Spring、SpringMVC、Mybatis、Maven
问题、报错、解决等都和做项目过程中遇到的bug等有关。
--------------------------------------------------------------------------------------
尚筹网项目-目录1
01-尚硅谷-尚筹网-简介
02-环境搭建
1 环境搭建总体目标
2 创建工程
3 创建数据库和数据库表
4 基于Maven的MyBatis逆向工程
5 父工程依赖管理
6 Spring整合MyBatis
7 日志系统
8 声明式事务
9 表述层配置 (视频中为10)
10 SpringMVC环境下的Ajax请求
11 异常映射
13 声明一个类管理常量
14 引入前端静态资源
15 创建后台管理员登录页面(后台首页)
16 使用layer弹层组件
17 进化system-error.jsp
04-后台管理系统-管理员登录
1 目标
2 思路
3 代码
4 抽取后台主页面公共部分
5 登录状态的检查
05-后台管理系统-管理员维护
1 任务清单
2 分页
3 关键词查询
4 单条删除
5 新增
6 更新
06-后台管理系统-RBAC权限控制模型
-Ajax的同步和异步请求
1 异步的工作方式
2 同步的工作方式
3 本质
07-后台管理系统-角色维护
1 角色分页操作
2 角色维护
3 角色保存操作
4 角色更新操作
5 角色删除操
08-后台管理系统-菜单维护
1 树形结构基础知识介绍
2 菜单维护:页面显示树形结构
3 菜单维护:添加子节点
4 菜单维护:更新节点
5 菜单维护:删除节点
- @RestController = @Controller + @ResponseBody
09-后台管理系统-分配
1 权限控制
2 给Admin分配Role
3 给Role分配Auth
4 给Menu分配Auth
-----------------------------------------------------------------------------------------
对项目进行知识储备↓
SpringSecurity
1 SpringSecrity框架用法介绍
2 权限管理过程中的相关概念
3 权限管理的主流框架
4 使用配置类代替XML
5 HelloWorld 工程创建步骤
6 在HelloWorld基础上加入SpringSecurity
7 SpringSecurity操作实验
09-后台管理系统-分配
1 SpringSecurity回顾
2 项目中加入SpringSecurity
。。。。。。
-----------------------------------------------------------------------------------------
尚筹网项目-目录2
2 尚筹网会员系统总目标
3 会员系统架构
4 parent工程
5 搭建环境约定
6 eureka工程
7 entity工程
(entity项目和Admin-entity项目一样 失效)
之后只做对尚筹网官方文档的补充
redis
-----------------------------------------------------------------------------------------
报错
星图(众筹项目知识体系)
阿里支付宝开放平台
---------------------------------------------------------------------------------------------------------------------
01-尚硅谷-尚筹网-简介
后台管理员使用单一架构开发,前台会员系统使用分布式架构开发。
管理员↓
左右对应
项目架构:
02-环境搭建
1 环境搭建总体目标
2 创建工程
2.1 项目架构图
parent——父工程(去聚合)
webui、component、entity——子工程(被模块)
2.2 工程创建计划
atcrowdfunding01-admin-parent
atcrowdfunding02-admin-webui
atcrowdfunding03-admin-component
atcrowdfunding04-admin-entity
atcrowdfunding05-common-util
atcrowdfunding06-common-reverse
2.3 Maven工程和Maven模块
父工程创建(maven project):
archetypeCatalog
internal
子工程创建(maven module):
最终结果展示:
2.4 建立工程之间的依赖关系
(开始SSM整合,步骤:Spring+MyBatis→Spring+SpringMVC)
开始Spring+MyBatis整合:
3 创建数据库和数据库表
3.1 物理建模
3.2 创建数据库
CREATE DATABASE fundingonline_study CHARACTER SET utf8;
3.3 创建管理员数据库表
use fundingonline_study; DROP TABLE IF EXISTS `t_admin`;
CREATE TABLE `t_admin` ( `id` int(11) NOT NULL AUTO_INCREMENT, # 主键 `login_acct` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, # 登录账号 `user_pswd` char(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, # 登录密码 `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, # 昵称 `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, # 邮箱地址 `create_time` char(19) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, # 创建时间 PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `login_acct`(`login_acct`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 251 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
4 基于Maven的MyBatis逆向工程
4.1 pom配置
<!--依赖MyBatis核心包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
<!-- 控制 Maven 在构建过程中相关配置 --> <build> <finalName>atcrowdfunding006-common-reverse</finalName> <!-- 构建过程中用到的插件 --> <plugins> <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.0</version> <!-- 插件的依赖 --> <dependencies> <!-- 逆向工程的核心依赖 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.2</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.8</version> </dependency> </dependencies> </plugin> </plugins> </build>
|
4.2 generatorConfig.xml(逆向工程的文件,只需复制粘贴不必背)
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- mybatis-generator:generate --> <context id="atguiguTables" targetRuntime="MyBatis3"> <commentGenerator> <!-- 是否去除自动生成的注释 true:是;false:否 --> <property name="suppressAllComments" value="true" /> </commentGenerator> <!--数据库连接的信息:驱动类、连接地址、用户名、密码 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/project_crowd?useUnicode=true&characterEncoding=utf8" userId="root" password="pq21ce"> </jdbcConnection> <!-- 默认 false,把 JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true 时把 JDBC DECIMAL 和 NUMERIC 类型解析为 java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- targetProject:生成 Entity 类的路径 --> <javaModelGenerator targetProject=".\src\main\java" targetPackage="com.atguigu.crowd.entity"> <!-- enableSubPackages:是否让 schema 作为包的后缀 --> <property name="enableSubPackages" value="false" /> <!-- 从数据库返回的值被清理前后的空格 --> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- targetProject:XxxMapper.xml 映射文件生成的路径 --> <sqlMapGenerator targetProject=".\src\main\java" targetPackage="com.atguigu.crowd.mapper"> <!-- enableSubPackages:是否让 schema 作为包的后缀 --> <property name="enableSubPackages" value="false" /> </sqlMapGenerator> <!-- targetPackage:Mapper 接口生成的位置 --> <javaClientGenerator type="XMLMAPPER" targetProject=".\src\main\java" targetPackage="com.atguigu.crowd.mapper"> <!-- enableSubPackages:是否让 schema 作为包的后缀 --> <property name="enableSubPackages" value="false" /> </javaClientGenerator> <!-- 数据库表名字和我们的 com.atguigu.crowd.entity 类对应的映射指定 --> <!-- <table tableName="t_auth" domainObjectName="Auth" />--> <!-- <table tableName="t_admin" domainObjectName="Admin" />--> <table tableName="t_role" domainObjectName="Role" />
</context> </generatorConfiguration>
|
4.3 执行逆向生成操作的Maven命令
mybatis-generator:generate
注意选择要逆向的数据库表:
4.4 逆向工程生成的资源各归各位
归类好的情形:
出现问题:
①import com.stguigu.crowd.entity.Admin;语句爆红
②import org.apache.ibatis.annotations.Param;语句爆红
问题①解决方法:
问题②解决办法:
导包→mybatis
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
5 父工程依赖管理
5.1 版本声明
注:spring4.3.20 匹配 json2.9.8(需要自己配置json版本原因:SpringMVC用到了json却没有配置json,所以手动配置json版本时要注意版本匹配是否匹配问题)
5.2 依赖管理
<dependencyManagement> <dependencies> <!-- Spring 依赖 --> <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <!--<exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions>--> <version>${atguigu.spring.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${atguigu.spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${atguigu.spring.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency> <!-- 数据库依赖 --> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.3</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.0.0</version> </dependency> <!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 其他日志框架的中间转换包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> 爆红解决方法:下载该jar包,手动导入jar文件 <version>1.7.25</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>1.7.25</version> </dependency> <!-- Spring 进行 JSON 数据转换依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.8</version> </dependency> <!-- JSTL 标签库 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- junit 测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- 引入 Servlet 容器中相关依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- JSP 页面使用的依赖 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1.3-b06</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> </dependency> </dependencies> </dependencyManagement>
|
5.3 依赖信息来源
Maven jar包网站:mvnrepository.com
6 Spring整合MyBatis
6.1目标
6.2 思路
核心目标:将adminMapper装配进IOC
6.3 操作清单
在子工程中加入搭建环境所需要的具体依赖
准备jdbc.properties
创建Spring配置文件专门配置Spring和Mybatis整合相关
在Spring配置文件中加载jdbc.properties属性文件
配置数据源
测试从数据源中获取数据库连接
配置SqlSessionFactoryBean
装配数据源
指定xxxMapper.xml配置文件的位置
指定mybatis全局配置文件的位置(可选)
配置MapperScannerConfigurer
测试是否可以装配xxxMappper接口并通过这个家口操作数据库
6.4 操作步骤详解
6.4.1 在子工程中加入搭建环境所需要的具体依赖
子工程:选择component工程。原因:具体依赖和component工程相关。
<dependency> <groupId>com.atguigu.crowd</groupId> <artifactId>atcrowdfunding005-common-util</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- Spring 依赖 --> <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> <!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency> <!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <!-- 其他日志框架的中间转换包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency>
<!-- Spring 进行 JSON 数据转换依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </dependency>
<!-- JSTL 标签库 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency>
|
6.4.2 jdbc.properties
jdbc.user=root jdbc.password=pq21ce jdbc.url=jdbc:mysql://localhost:3306/fundingonline_study?useUnicode=true&characterEncoding=UTF-8 jdbc.driver=com.mysql.jdbc.Driver
|
6.4.3 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>
</configuration>
|
6.4.4 spring-persist-mybatis.xml
Spring具体配置:
1、配置数据源、测试
<!--加载外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置数据源--> <!-- 配置数据源 com.alibaba.druid.pool.DruidDataSource--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- 连接数据库的用户名 --> <property name="username" value="${jdbc.user}"/> <!-- 连接数据库的密码 --> <property name="password" value="${jdbc.password}"/> <!-- 目标数据库的 URL 地址 --> <property name="url" value="${jdbc.url}"/> <!-- 数据库驱动全类名 --> <property name="driverClassName" value="${jdbc.driver}"/> </bean>
|
创建Spirng的Junit测试类 package com.atguigu.crowd;
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;
/** * @author 123 * date 2022-09-18 */ // 在类上标记必要的注解,spring整合junit @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml"}) public class CrowdTest {
@Autowired private DataSource dataSource;
@Test public void testConnection() throws SQLException { Connection connection = dataSource.getConnection(); System.out.println(connection); } }
|
<!--junit和spring-test无法传递 那个module需要则在那个module导入--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>
|
2、配置SqlSessionFactoryBean
<!-- 配置SqlSessionFactoryBean整合Mybatis --> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--指定Mybatis全局配置文件的位置--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!--指定Mapper.xml的位置--> <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"></property> <!--装配数据源--> <property name="dataSource" ref="dataSource"></property> <!-- 配置插件--> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <props> <!--配置数据库方言,告诉PageHelper当前使用的数据库--> <prop key="dialect">mysql</prop> <!--配置页码的合理化修正: 总页数只有20页, 若用户输入跳转至50页,则显示第20页; 若用户输入跳转至-5页,则显示第1页。 --> <prop key="reasonable">true</prop> </props> </property> </bean> </array> </property> </bean> <!-- 配置MapperScannerConfigurer来扫描Mapper接口所在的包--> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.atguigu.crowd.mapper"></property> </bean>
|
package com.atguigu.crowd;
import com.atguigu.crowd.entity.Admin; import com.atguigu.crowd.mapper.AdminMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;
/** * @author 123 * date 2022-09-18 */ // 在类上标记必要的注解,spring整合junit // 指定Spirng给Junit提供的运行器类 @RunWith(SpringJUnit4ClassRunner.class) // 加载Spring配置文件的注解 @ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml"}) public class CrowdTest {
@Autowired private AdminMapper adminMapper;
@Test public void testInertAdmin() { Admin admin = new Admin(null, "tom", "123123", "汤姆", "tom@qq.com", null); int count = adminMapper.insert(admin); System.out.println("受影响的行数:" + count); } }
|
测试报错:
解决:将tom改成其他值即可,因为值重复了、
总览:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!--加载外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置数据源--> <!-- 配置数据源 com.alibaba.druid.pool.DruidDataSource--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- 连接数据库的用户名 --> <property name="username" value="${jdbc.user}"/> <!-- 连接数据库的密码 --> <property name="password" value="${jdbc.password}"/> <!-- 目标数据库的 URL 地址 --> <property name="url" value="${jdbc.url}"/> <!-- 数据库驱动全类名 --> <property name="driverClassName" value="${jdbc.driver}"/> </bean> <!-- 配置SqlSessionFactoryBean整合Mybatis --> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--指定Mybatis全局配置文件的位置--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!--指定Mapper.xml的位置--> <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"></property> <!--装配数据源--> <property name="dataSource" ref="dataSource"></property> <!-- 配置插件--> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <props> <!--配置数据库方言,告诉PageHelper当前使用的数据库--> <prop key="dialect">mysql</prop> <!--配置页码的合理化修正: 总页数只有20页, 若用户输入跳转至50页,则显示第20页; 若用户输入跳转至-5页,则显示第1页。 --> <prop key="reasonable">true</prop> </props> </property> </bean> </array> </property> </bean> <!-- 配置MapperScannerConfigurer来扫描Mapper接口所在的包--> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.atguigu.crowd.mapper"></property>
</bean> </beans>
|
7 日志系统
7.1重要意义
7.2 技术选型
7.2.1 总体介绍
7.2.2 不同日志系统的整合
7.3 具体操作
7.3.1 初始状态
7.3.2 加入slf4j+logback
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
|
代码不变,日志情况是:
8.3.3 我们主动打印的日志
// 1、获取Logger对象,这里传入的Class对象就是当前打印日志的类 Logger logger = LoggerFactory.getLogger(CrowdTest.class);
// 2、根据不同日志级别打印日志 logger.debug("debug level!!!"); logger.debug("debug level!!!"); logger.debug("debug level!!!");
logger.info("info lecel!!!"); logger.warn("warn level!!!"); logger.error("error levell!!");
|
效果:
7.3.4 更换框架日志系统
第一步:排除commons-logging
component: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>
webui:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>
|
此时,会报错(一开始使用的依赖就已经添加了排除commons-logging的代码且添加了jcl-over-slf4j包,所以不会报错):
当添加了jcl-over-slf4j依赖时,报错解除:
<!-- 其他日志框架的中间转换包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version> </dependency>
|
7.3.5 logback配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体 内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="INFO"> <!-- 指定打印日志的 appender,这里通过“STDOUT”引用了前面配置的 appender --> <appender-ref ref="STDOUT" /> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="com.atguigu.crowd.mapper" level="DEBUG"/> </configuration>
|
结果展示:
8 声明式事务
8.1 目标
8.2 思路
8.2.1 选择合适的事务管理器
8.2.2 配置文件结构
8.3 代码
8.3.1 创建Spring专门管理事务的配置文件
添加名称空间
8.3.2 配置自动扫描的包
<context:component-scan base-package="com.atguigu.crowd.service"/>
|
8.3.3 具体配置
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--配置自动扫描的包,主要是为了把Service扫描到IOC容器中--> <context:component-scan base-package="com.atguigu.crowd.service"/> <!--配置事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--装配数据源--> <!--这个ref="dataSource"是spring-persist-mybatis.xml中数据源id名,写了ref="dataSource"后会提示:dataSource无法找到,但是只要运行项目时,spring-persist-mybatis.xml和spring-persist-tx.xml这俩文件都运行了,它就能找到--> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务切面--> <aop:config> <!--考虑需要整合SpringSecurity,避免UserDetailsService加入事务控制,让切入点表达式定位到ServiceImpl而不是Service--> <aop:pointcut id="txPointcut" expression="execution(* *..*ServiceImpl.*(..))"/> <!--将切入点表达式和事务通知关联起来--> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"></aop:advisor> </aop:config> <!--配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="txManager"> <!--配置事务的属性--> <tx:attributes> <!--查询方法:配置只读属性,让数据库知道这是一个查询操作,能够进行一定优化--> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"></tx:method> <tx:method name="query*" read-only="true"></tx:method> <tx:method name="count*" read-only="true"></tx:method> <!--增删改方法:配置事务传播行为,回滚异常--> <!--propagation: REQUIRES:受其他事务回滚影响。表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,则自己开新事务;如果已经有了,那么就使用这个已有的事务,但是用别人的事务,若别人回滚,他自己也会回滚。 REQUIRES_NEW:不受其他事务回滚的印影响。表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,则自己开新事务;如果已经有了,也在自己开启的事务中运行。--> <!--rollback-for:配置事务方法针对什么样的异常回滚 默认:运行时回滚 建议:编译时异常和运行时异常都回滚--> <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"></tx:method> <tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"></tx:method> <tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"></tx:method> <tx:method name="batch*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"></tx:method> </tx:attributes>
</tx:advice> </beans>
|
8.4 测试
package com.atguigu.crowd.service.impl;
import com.atguigu.crowd.entity.Admin; import com.atguigu.crowd.mapper.AdminMapper; import com.atguigu.crowd.service.api.AdminService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
/** * @author 123 * date 2022-01-05 */ @Service public class AdminServiceImpl implements AdminService {
@Autowired private AdminMapper adminMapper;
@Override public void saveAdmin(Admin admin) { adminMapper.insert(admin); }
}
|
crowdtest.java中:
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml", "classpath:spring-persist-tx.xml"}) // 来源spring-test依赖
@Autowired private AdminService adminService;
@Test public void testTx() { Admin admin = new Admin(null, "m", "123123", "汤姆", "tom@qq.com", null); adminService.saveAdmin(admin); }
|
8.5 注意
9 表述层配置
9.1目标
9.2 思路
web.xml和Spring配置关系:
9.3 web.xml配置
9.3.1 配置ContextLoaderListener
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-persist-*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
|
9.3.2 配置CharacterEncodingFilter
<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>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
9.3.3 配置DispatcherServlet
<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-web-mvc.xml,classpath:spring-persist-*.xml</param-value> </init-param> <!--Servlet默认生命周期中,创建对象是第一次接受到请求时--> <!--DispatcherServlet创建对象后有大量的框架初始化工作,不适合在第一次请求时来做--> <!--这样设置就是为了让DispatcherServlet在web应用启动时创建对象、初始化--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!--1.url-pattern 表示拦截所有请求--> <!--<url-pattern>/</url-pattern>--> <!--2.配置请求扩展名 优点: 1.css js png等等静态资源完全不经过Springmvc,不需要特殊处理 2.实现伪静态效果。表面上看起来访问一个html文件这样的静态资源,但实际上是经过java代码运算过的 作用:给黑客入侵增加难度 利于SEO优化(让搜索引擎更容易找到我们项目) 缺点: 不符合RESTFul风格。--> <url-pattern>*.html</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping>
|
常见错误:
9.4 代码:SpringMVC配置
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 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-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
<context:component-scan base-package="com.atguigu.crowd.mvc"/>
<mvc:annotation-driven/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value=""/> <property name="suffix" value=".jsp"/> </bean>
</beans>
|
9.5 测试
9.5.1 在webui工程添加依赖
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <scope>provided</scope> </dependency>
|
9.5.2 创建index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <body>
<a href="${pageContext.request.contextPath}/test/ssm.html">测试SSM整合环境</a>
</body> </html>
|
9.6 base标签
<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/"/> <%--${pageContext.request.contextPath}前面自带斜杠--%> <%--绝对路径:${pageContext.request.contextPath}--%>
|
10 SpringMVC环境下的Ajax请求
10.1 建立意识
10.2 常用注解
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
|
10.3 @RequestBody注解的使用
10.3.1 引入jQuery
<script type="text/javascript" src="jquery/jquery-2.1.1.min.js"></script>
10.3.2 发送数组到服务器端:方案一
jQuery代码
$(function () { $("#btn1").click(function () { $.ajax({ "url": "send/array/one.html", // 请求目标资源的地址 "type": "post", // 请求方式 "data": { // 要发送的请求参数 "array": [5,8,12] }, "dataType": "text", // 如何对待服务器端返回的数据 "success": function (response) { // 服务器端处理请求成功后调用的回调函数,response是响应体数据 alert(response); }, "error": function (response) { // // 服务器端处理请求失败后调用的回调函数,response是响应体数据 alert(response); } }); }) });
|
handler代码
@ResponseBody @RequestMapping("send/array/one.html") public String testReceiveArrayOne(@RequestParam("array[]") List<Integer> array) { for (Integer number: array) { System.out.println("number=" + number); } return "target"; }
|
10.3.3 发送数组到服务器端:方案二
jQuery代码
$("#btn2").click(function () { $.ajax({ "url": "send/array/two.html", // 请求目标资源的地址 "type": "post", // 请求方式 "data": { // 要发送的请求参数 "array[0]": 5, "array[1]": 8, "array[2]": 12 }, "dataType": "text", // 如何对待服务器端返回的数据 "success": function (response) { // 服务器端处理请求成功后调用的回调函数,response是响应体数据 alert(response); }, "error": function (response) { // // 服务器端处理请求失败后调用的回调函数,response是响应体数据 alert(response); } }); });
|
handler代码
@ResponseBody @RequestMapping("/send/array/two.html") public String testReceiveArrayTwo(ParamData paramData) { List<Integer> array = paramData.getArray(); for (Integer number : array) { System.out.println("number=" + number); } return "success"; }
|
ParamData代码
package com.atguigu.crowd.entity;
import java.util.List;
public class ParamData {
private List<Integer> array;
public ParamData() { }
public ParamData(List<Integer> array) { this.array = array; }
public List<Integer> getArray() { return array; }
public void setArray(List<Integer> array) { this.array = array; }
@Override public String toString() { return "ParamData{" + "array=" + array + '}'; } }
|
10.3.4 发送数组到服务器端:方案三
在浏览器开发者工具中看到的请求体:
不同于法二,是request payload不是form data了
jQuery代码:
$("#btn3").click(function () { // 准备好要发送到服务器端的数组 var array = [5,8,12]; console.log(array.length);
// 将JSON数组转换为JSON字符串 var requestBody = JSON.stringify(array); // "['5','8','12']" console.log(requestBody.length);
$.ajax({ "url": "send/array/three.html", // 请求目标资源的地址 "type": "post", // 请求方式 "data": requestBody, // 请求体 "contentType": "application/json;charset=UTF-8", // 设置请求体的内容类型,告诉服务器本次请求的请求体是JSON数据 "dataType": "text", // 如何对待服务器端返回的数据 "success": function (response) { // 服务器端处理请求成功后调用的回调函数,response是响应体数据 alert(response); }, "error": function (response) { // // 服务器端处理请求失败后调用的回调函数,response是响应体数据 alert(response); } }); });
|
handler代码
@ResponseBody @RequestMapping("/send/array/three.html") public String testReceiveArrayThree(@RequestBody List<Integer> array) { for (Integer number : array) { logger.info("number=" + number); } return "success"; }
|
10.4 发送复杂的对象
10.4.1 代码:
package org.example.entity;
import java.util.List; import java.util.Map;
/** * @author 123 * date 2023-01-07 */ public class Student {
private Integer stuId; private String stuName; private Address address; private List<Subject> subjectList; private Map<String, String> map;
public Student() { }
public Student(Integer stuId, String stuName, Address address, List<Subject> subjectList, Map<String, String> map) { this.stuId = stuId; this.stuName = stuName; this.address = address; this.subjectList = subjectList; this.map = map; }
public Integer getStuId() { return stuId; }
public void setStuId(Integer stuId) { this.stuId = stuId; }
public String getStuName() { return stuName; }
public void setStuName(String stuName) { this.stuName = stuName; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public List<Subject> getSubjectList() { return subjectList; }
public void setSubjectList(List<Subject> subjectList) { this.subjectList = subjectList; }
public Map<String, String> getMap() { return map; }
public void setMap(Map<String, String> map) { this.map = map; }
@Override public String toString() { return "Student{" + "stuId=" + stuId + ", stuName='" + stuName + '\'' + ", address=" + address + ", subjectList=" + subjectList + ", map=" + map + '}'; } }
|
package org.example.entity;
/** * @author 123 * date 2023-01-07 */ public class Address {
private String province; private String city; private String street;
public Address() { }
public Address(String province, String city, String street) { this.province = province; this.city = city; this.street = street; }
public String getProvince() { return province; }
public void setProvince(String province) { this.province = province; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
@Override public String toString() { return "Address{" + "province='" + province + '\'' + ", city='" + city + '\'' + ", street='" + street + '\'' + '}'; } }
|
package org.example.entity;
/** * @author 123 * date 2023-01-07 */ public class Subject {
private String subjectName; private String subjectScore;
public Subject() { }
public Subject(String subjectName, String subjectScore) { this.subjectName = subjectName; this.subjectScore = subjectScore; }
public String getSubjectName() { return subjectName; }
public void setSubjectName(String subjectName) { this.subjectName = subjectName; }
public String getSubjectScore() { return subjectScore; }
public void setSubjectScore(String subjectScore) { this.subjectScore = subjectScore; }
@Override public String toString() { return "Subject{" + "subjectName='" + subjectName + '\'' + ", subjectScore='" + subjectScore + '\'' + '}'; } }
|
TestHandler.java中
@ResponseBody @RequestMapping("/send/compose/object.html") public String testReceiveComposeObject(@RequestBody Student student) { logger.info(student.toString()); return "success"; }
|
index.jsp中
$("#btn4").click(function () {
// 准备要发送的数据 var student = { "stuId": 5, "stuName": "tom", "address": { "province": "广东", "city": "深圳", "street": "后端" }, "subjectList": [ { "subjectName": "JavaSE", "subjectScore": 99 },{ "subjectName": "SSM", "subjectScore": 90 } ], "map": { "k1": "v1", "k2": "v2" } };
// 将JSON对象转换为JSON字符串 var requestBody = JSON.stringify(student); // 辨析payload时要加这步
// 发送ajax请求 $.ajax({ "url":"send/compose/object.html", "type":"post", "data":requestBody, "contentType":"application/json;charset=UTF-8", "dataType":function (response) { alert(response); }, "error":function (response) { alert(response); } });
});
<button id="btn4">Send Compose Object</button>
|
10.4.2 结果:
10.5 对Ajax请求返回的结果进行规范
10.5.1 代码
package org.example.util;
/** * @author 123 * date 2023-01-12 * * 统一项目中AJAX请求返回的结果,也可以用于分布式架构中各个模块间调用时返回统一类型 */ public class ResultEntity<T> {
public static final String SUCCESS ="SUCCESS"; public static final String FAILED = "FAILED";
// 用来封装当前请求处理的结果是成功还是失败 private String result;
// 请求处理失败时返回的错误消息 private String message;
// 要返回的数据 private T data;
//请求处理成功且不需要返回数据时使用的工具方法 public static <Type> ResultEntity<Type> successWithoutData() { return new ResultEntity<Type>(SUCCESS, null, null); } //请求处理成功且需要数据时使用的工具方法 data:要返回的数据 public static <Type> ResultEntity<Type> successWithData(Type data) { return new ResultEntity<Type>(SUCCESS, null, data); } //请求处理失败后使用的工具方法 message:失败的错误消息 public static <Type> ResultEntity<Type> failed(String message) { return new ResultEntity<Type>(FAILED, message, null); }
public ResultEntity() { }
public ResultEntity(String result, String message, T data) { this.result = result; this.message = message; this.data = data; }
public String getResult() { return result; }
public void setResult(String result) { this.result = result; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
@Override public String toString() { return "ResultEntity{" + "result='" + result + '\'' + ", message='" + message + '\'' + ", data=" + data + '}'; } }
|
TestHandler.java中:
@ResponseBody @RequestMapping("/send/compose/object.json") public ResultEntity<Student> testReceiveComposeObject(@RequestBody Student student) { logger.info(student.toString()); return ResultEntity.successWithData(student); // 将查询到的Student对象封装到ResultEntity中返回 }
|
index.jsp → $("#btn4") 中:
"url":"send/compose/object.json",
"dataType":"json",
|
10.5.2 结果
报错:406
解决:将.html改为.json
11 异常映射
11.1 目标
11.2 思路
note:
<!-- @RequestMapping("/xxx/xxx.html") public String xxx(){ return "target"; } 如此,无方法体的controller可以直接配置xml代替该方法: --> <mvc:view-controller path="/xxx/xxx.html" view-name="target"/>
|
11.3 代码
11.3.1 基于XML的异常映射
<!--view-Controller--> <!--<mvc:view-controller path="" view-name=""></mvc:view-controller>--> <!--基于基于XML的异常映射--> <bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!--配置异常类型和具体视图页面的对应关系--> <property name="exceptionMappings"> <props> <!--key属性指定异常全类名--> <!--标签体中写对应的视图,这个值要拼前后缀得到具体路径--> <prop key="java.lang.Exception">system-error</prop> </props> </property> </bean>
|
11.3.2 判断请求类型的工具方法
加依赖:
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency>
|
请求类型 判断的依据:
开发者模式下
创建工具类,编写工具方法:
/* * 判断当前请求是否为ajax请求 * true:是 false:不是 * */ public static boolean judgeRequestType(HttpServletRequest request) {
// 1.获取请求消息头 String acceptHeader = request.getHeader("Accept"); String xRequestHeader = request.getHeader("X-Requested-With");
// 2.判断 return (acceptHeader != null && acceptHeader.contains("application/json"))
||
(xRequestHeader != null && xRequestHeader.equals("XMLHttpRequest")); }
|
11.3.3 基于注解的异常映射(需先判断请求类型为普通还是ajax)
代码:
package org.example.mvc.config;
import com.google.gson.Gson; import com.sun.org.apache.xpath.internal.operations.Mod; import org.example.util.CrowdUtil; import org.example.util.ResultEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
/** * @author 123 * date 2023-01-13 * * 表示当前是一个基于注解异常处理器类 */ @ControllerAdvice public class CrowdExceptionResolver {
// 数学异常 @ExceptionHandler(value = ArithmeticException.class) public ModelAndView resolveMathException( ArithmeticException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "system-error";
return commonResolve(viewName, exception, request, response); }
// 一个异常类型对应一个方法 // @ExceptionHandler注解 将一个具体异常类型与一个方法关联起来 // 空指针异常 @ExceptionHandler(value = NullPointerException.class) public ModelAndView resolveNullPointerException( NullPointerException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 异常处理完成后要去的页面 String viewName = "system-error";
return commonResolve(viewName, exception, request, response); }
// 通用方法,用于提取重复的代码 private ModelAndView commonResolve(
String viewName,
// 实际捕获到的异常类型 Exception exception,
// 当前请求对象 HttpServletRequest request,
// 当前响应对象 HttpServletResponse response ) {
//获取异常消息 String message = exception.getMessage();
//1.判断当前请求的类型 boolean judgeResult = CrowdUtil.judgRequestType(request); //2.如果是一个ajax请求 if (judgeResult) { //3.创建ResultEntity对象 ResultEntity<Object> failed = ResultEntity.failed(message); //4.创建Gson对象 Gson gson = new Gson(); //5.将对象转换为Json字符串 String json = gson.toJson(failed); //6.将Json字符串作为响应体返回给浏览器 try { response.getWriter().write(json); } catch (IOException e) { e.printStackTrace(); } //7.由于上面已经通过原生response对象返回了响应,所以不提供ModelAndView对象 return null; } //不是ajax请求 ModelAndView modelAndView = new ModelAndView(); //1.将exception对象存入模型 modelAndView.addObject("exception", exception); //2.设置对应视图 modelAndView.setViewName(viewName); //3.返回ModelAndView对象 return modelAndView; }
}
|
TestHandler.java中:
@ResponseBody @RequestMapping("/send/compose/object.json") public ResultEntity<Student> testReceiveComposeObject(@RequestBody Student student, HttpServletRequest request) {
boolean judgeResult = CrowdUtil.judgRequestType(request); logger.info("judgeResult="+judgeResult);
String a = null; System.out.println(a.length());
logger.info(student.toString()); return ResultEntity.successWithData(student); // 将查询到的Student对象封装到ResultEntity中返回 }
|
13 声明一个类管理常量
public class CrowdConstant {
public static final String MESSAGE_LOGIN_FAILED = "抱歉!账号密码错误!请重新输入!";
public static final String MESSAGE_LOGIN_ACCT_ALREADY_IN_USE = "抱歉!这个账号已经被使用了";
public static final String MESSAGE_ACCESS_FORBIDEN = "请登录以后再访问!";
public static final String ATTR_NAME_EXCEPTION = "exception";
}
|
14 引入前端静态资源
15 创建后台管理员登录页面(后台首页)
15.1 创建admin-login.jsp
<%-- Created by IntelliJ IDEA. User: Date: 2023/1/15 Time: 9:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="keys" content=""> <meta name="author" content=""> <base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/"> <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="css/font-awesome.min.css"> <link rel="stylesheet" href="css/login.css"> <script src="jquery/jquery-2.1.1.min.js"></script> <script src="bootstrap/js/bootstrap.min.js"></script> <style>
</style> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <div><a class="navbar-brand" href="index.html" style="font-size:32px;">众筹网</a></div> </div> </div> </nav>
<div class="container">
<form action="admin/do/login.html" method="post" class="form-signin" role="form"> <%--<p>${SPRING_SECURITY_LAST_EXCEPTION }</p> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>--%>
<h2 class="form-signin-heading"> <i class="glyphicon glyphicon-log-in"></i> 管理员登录 </h2> <p>${requestScope.execption.message }</p> <div class="form-group has-success has-feedback"> <input type="text" name="loginAcct" class="form-control" id="inputSuccess3" placeholder="请输入登录账号" autofocus> <span class="glyphicon glyphicon-user form-control-feedback"></span> </div> <div class="form-group has-success has-feedback"> <input type="text" name="userPswd" class="form-control" id="inputSuccess4" placeholder="请输入登录密码" style="margin-top:10px;"> <span class="glyphicon glyphicon-lock form-control-feedback"></span> </div> <button type="submit" class="btn btn-lg btn-success btn-block">登录</button> </form> </div>
</body> </html>
|
15.2 跳转到登录页面
<mvc:view-controller path="/admin/to/login/page.html" view-name="admin-login"/>
|
16 使用layer弹层组件
16.1 加入layer库文件和样式文件
16.2 在页面上引入layer环境
<script type="text/javascript" src="layer/layer.js"></script>
|
16.3 使用layer弹层提示框
index.jsp中:
layer.msg("Layer的弹框");
|
17 进化system-error.jsp
<%-- Created by IntelliJ IDEA. User: 郎贵雯 Date: 2023/1/15 Time: 9:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="keys" content=""> <meta name="author" content=""> <base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/"> <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="css/font-awesome.min.css"> <link rel="stylesheet" href="css/login.css"> <script src="jquery/jquery-2.1.1.min.js"></script> <script src="bootstrap/js/bootstrap.min.js"></script> <script type="text/javascript"> $(function () { $("button").click(function () { //相当于后退 window.history.back(); }); }); </script> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <div><a class="navbar-brand" href="index.html" style="font-size:32px;">众筹网</a></div> </div> </div> </nav>
<div class="container">
<h2 class="form-signin-heading" style="text-align: center;"> <i class="glyphicon glyphicon-log-in"></i> 众筹网系统消息 </h2> <%-- requestScope对应的是存放request域数据的Map requestScope.exception == request.getAttribute("exception") requestScope.exception.message == exception.getMessage() --%> <h3 style="text-align: center;">${requestScope.exception.message }</h3> <button style="width:150px;margin: 50px auto 0px auto" class="btn btn-lg btn-success btn-block">点我返回上一步</button>
</div>
</body> </html>
|
04-后台管理系统-管理员登录
1 目标
2 思路
3 代码
3.1 创建工具方法执行MD5加密
/** * 对明文字符串进行MD5加密 * @param source 传入的明文字符串 * @return 加密的结果 */ public static String md5(String source) {
// 1.判断source是否有效 if(source == null || source.length() == 0) {
// 2.若非有效的字符串抛出异常 throw new RuntimeException(CrowdConstant.MESSAGE_STRING_INVALIDATE); }
try { // 3.获取MessageDigest对象 String algorithm = "md5"; MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 4.获取明文字符串对应的字节数组 byte[] input = source.getBytes();
// 5.执行加密 byte[] output = messageDigest.digest(input);
// 6.创建BigInteger对象 int signum = 1; BigInteger bigInteger = new BigInteger(signum, output);
// 7.按照16进制将bigInteger的值转换为字符串 int radix = 16; String encoded = bigInteger.toString(radix).toUpperCase();
return encoded;
} catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
return null; }
|
MD5测试:
package org.example.test;
import org.example.util.CrowdUtil; import org.junit.Test;
/** * @author 123 * date 2023-01-15 */ public class StringTest {
@Test public void testMd5() { String source = "123123"; String encoded = CrowdUtil.md5(source); System.out.println(encoded); } }
|
3.2 自定义异常——登陆失败
package org.example.exception;
/** * @author 123 * date 2023-01-15 * * 自定义异常:登录失败后抛出异常 */ public class LoginFailedException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LoginFailedException() { }
public LoginFailedException(String message) { super(message); }
public LoginFailedException(String message, Throwable cause) { super(message, cause); }
public LoginFailedException(Throwable cause) { super(cause); }
public LoginFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }
|
3.3 异常处理器类中添加登录失败的处理
@ExceptionHandler(value = LoginFailedException.class) public ModelAndView resolveLoginFailedException( LoginFailedException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 异常处理完成后要去的页面 String viewName = "admin-login";
return commonResolve(viewName, exception, request, response); }
|
3.4 在登录页面显示异常消息
<h2 class="form-signin-heading"> <i class="glyphicon glyphicon-log-in"></i> 管理员登录 </h2> <p>${requestScope.execption.message }</p>
|
3.5 handler方法
package org.example.mvc.handler;
import org.example.constant.CrowdConstant; import org.example.entity.Admin; import org.example.service.api.AdminService; 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 javax.servlet.http.HttpSession;
/** * @author 123 * date 2023-01-15 */ @Controller public class AdminHandler {
@Autowired private AdminService adminService;
@RequestMapping("/admin/do/login.html") public String doLogin( @RequestParam("loginAcct") String loginAcct, @RequestParam("userPswd") String userPswd, HttpSession session ) {
// 调用Service方法执行登录检查 // 这个方法若能够返回admin对象说明登录成功,若账号、密码不正确则会抛出异常 Admin admin = adminService.getAdminByLoginAcct(loginAcct, userPswd);
// 将登录成功返回的admin对象存入Session域 session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN, admin);
return "admin-main"; }
}
|
3.6 service方法
AdminService.class中:
Admin getAdminByLoginAcct(String loginAcct, String userPswd);
|
AdminServiceImpl中:
@Override public Admin getAdminByLoginAcct(String loginAcct, String userPswd) {
// 1.根据登录账号查询admin对象 // 创建AdminExample对象 AdminExample adminExample = new AdminExample(); // 创建Criteria对象 AdminExample.Criteria criteria = adminExample.createCriteria(); // 在Criteria对象中封装查询条件 criteria.andLoginAcctEqualTo(loginAcct); // 调用AdminMapper的方法执行查询 List<Admin> admins = adminMapper.selectByExample(adminExample); // 2.查询Admin对象是否为空 if (admins == null && admins.size() == 0) { throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED); } if (admins.size() > 1) { throw new RuntimeException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE); } Admin admin = admins.get(0); // 3.如果admin对象为空则抛出异常 if (admin == null) { throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED); } // 4.如果admin对象不为null则将数据库密码从Admin对象中取出 String userPswdDB = admin.getUserPswd(); // 5.将表单提交的明文密码进行加密 String userPswdForm = CrowdUtil.md5(userPswd); // 6.对密码进行比较 if (!Objects.equals(userPswdDB, userPswdForm)) { // 7.如果比较结果是不一致则抛出异常 throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED); } // 8.如果一致则返回Admin对象 return admin; }
|
登录测试↓
登录页面:
登录成功页面:
登录失败页面:
???登录失败提示消息未完成,理应为如下情况:
3.7 补充
3.7.1 前往后台主页面的方式调整
@RequestMapping("/admin/do/login.html") public String doLogin( @RequestParam("loginAcct") String loginAcct, @RequestParam("userPswd") String userPswd, HttpSession session ) {
// 调用Service方法执行登录检查 // 这个方法若能够返回admin对象说明登录成功,若账号、密码不正确则会抛出异常 Admin admin = adminService.getAdminByLoginAcct(loginAcct, userPswd);
// 将登录成功返回的admin对象存入Session域 session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN, admin);
return "redirect:/admin/to/main/page.html"; }
|
需要给目标地址配置view-controller:
<mvc:view-controller path="/admin/to/main/page.html" view-name="admin-main"/>
|
3.7.2 退出登录
<li><a href="admin/do/logout.html"><i class="glyphicon glyphicon-off"></i> 退出系统</a></li>
|
@RequestMapping("/admin/do/logout.html") public String doLogout(HttpSession session) { //强制session失效 session.invalidate(); return "redirect:/admin/to/login/page.html"; }
|
4 抽取后台主页面公共部分
4.1 创建公共部分JSP
4.2 抽取后效果
<%-- Created by IntelliJ IDEA. User: Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp"%> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp"%> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> ......... </div> </div> </div>
<%--<h1>临时主页面</h1> ${sessionScope.loginAdmin.userName }--%>
</body> </html>
|
4.3 创建JSP模板
模板内容是:
众筹网后台JSP模板:
<%-- Created by IntelliJ IDEA. User: 郎贵雯 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp"%> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp"%> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> </div> </div> </div>
<%--<h1>临时主页面</h1> ${sessionScope.loginAdmin.userName }--%>
</body> </html>
|
5 登录状态的检查
5.1 目标
5.2 思路
5.3 代码
5.3.1 创建自定义异常
package org.example.exception; /* * 表示用户没有登录就访问受保护资源时抛出的异常 * * */ public class AccessForbiddenException extends RuntimeException{ private static final long serialVersionUID = 8730782733834090098L;
public AccessForbiddenException() { super(); }
public AccessForbiddenException(String s) { super(s); }
public AccessForbiddenException(String s, Throwable throwable) { super(s, throwable); }
public AccessForbiddenException(Throwable throwable) { super(throwable); }
protected AccessForbiddenException(String s, Throwable throwable, boolean b, boolean b1) { super(s, throwable, b, b1); } }
|
5.3.2 创建拦截器类
package org.example.mvc.interceptor;
import org.example.constant.CrowdConstant; import org.example.entity.Admin; import org.example.exception.AccessForbiddenException; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;
/** * @author 123 * date 2023-01-21 */ public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// 1.通过request对象获取Session对象 HttpSession session = httpServletRequest.getSession();
// 2.尝试从Session域中获取Admin对象 Admin admin = (Admin)session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN);
// 3.判断admin对象是否为空 if(admin == null) {
// 4.抛出异常 throw new AccessForbiddenException(CrowdConstant.MESSAGE_ACCESS_FORBIDEN);
}
// 5.若Admin对象不为null,则返回true就行 return true; }
}
|
5.3.3 注册拦截器类
<!--注册拦截器--> <mvc:interceptors> <mvc:interceptor> <!--mvc:mapping配置要拦截的资源--> <!--/*对应一层路径 /**对应多层路径--> <mvc:mapping path="/**"/>
<!--mvc:exclude-mapping配置不拦截的资源--> <mvc:exclude-mapping path="/admin/to/login/page.html"/> <mvc:exclude-mapping path="/admin/do/login.html"/> <mvc:exclude-mapping path="/admin/do/logout.html"/>
<!--配置拦截器类--> <bean class="org.example.mvc.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>
|
拦截器-测试结果:
05-后台管理系统-管理员维护
1 任务清单
2 分页
2.1 目标
2.2 思路
2.3 代码
2.3.1 引入PageHelper
确认是否加入了依赖:
<!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency>
|
在SQLSessionFactoryBean配置MyBatis插件:
<!-- 配置SqlSessionFactoryBean整合Mybatis --> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--指定Mybatis全局配置文件的位置--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!--指定Mapper.xml的位置--> <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"></property> <!--装配数据源--> <property name="dataSource" ref="dataSource"></property> <!-- 配置插件--> <property name="plugins"> <array> <!-- 配置PageHelper插件 --> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <props> <!--配置数据库方言,告诉PageHelper当前使用的数据库--> <prop key="dialect">mysql</prop> <!--配置页码的合理化修正--> <prop key="reasonable">true</prop> </props> </property> </bean> </array> </property> </bean>
|
2.3.2 AdminMapper中编写SQL语句
<select id="selectAdminByKeyword" resultMap="BaseResultMap"> select id, login_acct, user_pswd, user_name, email, create_time from t_admin where login_acct like concat("%",#{keyword},"%") or user_name like concat("%",#{keyword},"%") or email like concat("%",#{keyword},"%") </select>
|
2.3.3 AdminMapper接口中声明方法
List<Admin> selectAdminByKeyword(String keyword);
|
2.3.4 AdminService方法
AdminService.class
PageInfo<Admin> getPageInfo(String keyword, Integer pageNum, Integer pageSize);
|
AdminServiceImpl.class
@Override public PageInfo<Admin> getPageInfo(String keyword, Integer pageNum, Integer pageSize) { // 1.调用PageHelper的静态方法开启分页功能 PageHelper.startPage(pageNum, pageSize); // 2.执行查询 List<Admin> list = adminMapper.selectAdminByKeyword(keyword); // 3.封装到PageInfo对象中 return new PageInfo<>(list); }
|
2.3.5 AdminHandler方法
@RequestMapping("/admin/get/page.html") public String getPageInfo( @RequestParam(value = "keyword", defaultValue = "") String keyword, @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize, ModelMap modelMap) { // 调用service方法获取PageInfo对象 PageInfo<Admin> pageInfo = adminService.getPageInfo(keyword, pageNum, pageSize); // 将PageInfo对象存入模型 modelMap.addAttribute(CrowdConstant.ATTR_NAME_PAGE_INFO, pageInfo); return "admin-page"; }
|
2.3.6 显示主体页
<%-- Created by IntelliJ IDEA. User: Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><i class="glyphicon glyphicon-th"></i> 数据列表</h3> </div> <div class="panel-body"> <form action="admin/get/page.html" class="form-inline" role="form" style="float:left;"> <div class="form-group has-feedback"> <div class="input-group"> <div class="input-group-addon">查询条件</div> <input name="keyword" class="form-control has-success" type="text" placeholder="请输入查询条件"> </div> </div> <button type="submit" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i> 查询 </button> </form> <button type="button" class="btn btn-danger" style="float:right;margin-left:10px;"><i class=" glyphicon glyphicon-remove"></i> 删除 </button> <a style="float: right;" href="admin/to/add/page.html" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> 新增 </a> <br> <hr style="clear:both;"> <div class="table-responsive"> <table class="table table-bordered"> <thead> <tr> <th width="30">#</th> <th width="30"><input type="checkbox"></th> <th>账号</th> <th>名称</th> <th>邮箱地址</th> <th width="100">操作</th> </tr> </thead> <tbody> <c:if test="${empty requestScope.pageInfo.list }"> <td colspan="6" align="center">抱歉!没有查询到您要的数据!</td> </c:if> <c:if test="${!empty requestScope.pageInfo.list }"> <c:forEach items="${requestScope.pageInfo.list }" var="admin" varStatus="myStatus"> <tr> <td>${myStatus.count }</td> <td><input type="checkbox"></td> <td>${admin.loginAcct }</td> <td>${admin.userName }</td> <td>${admin.email }</td> <td> <button type="button" class="btn btn-success btn-xs"> <i class="glyphicon glyphicon-check"></i> </button> <button type="button" class="btn btn-primary btn-xs"> <i class="glyphicon glyphicon-pencil"></i> </button> <button type="button" class="btn btn-danger btn-xs"> <i class="glyphicon glyphicon-remove"></i> </button> </td> </tr> </c:forEach> </c:if> </tbody> <tfoot> <tr> <td colspan="6" align="center"> <ul class="pagination"> <li class="disabled"><a href="#">上一页</a></li> <li class="active"><a href="#">1 <span class="sr-only">(current)</span> </a> </li> <li><a href="#">2</a> </li> <li><a href="#">3</a> </li> <li><a href="#">4</a> </li> <li><a href="#">5</a> </li> <li><a href="#">下一页</a> </li> </ul> </td> </tr> </tfoot> </table> </div> </div> </div> </div> </div> </div> </body> </html>
|
2.3.7 在页面上使用Pagination实现页码导航条
导入Pagination环境(3个):
在有需要的页面上进行引入(注意先后顺序,Pagination要在jQuery的后面):
<%@include file="/WEB-INF/include-head.jsp" %> <!-- head.jsp中包含jQuery环境-->
<link rel="stylesheet" href="css/pagination.css"/> <script type="text/javascript" src="jquery/jquery.pagination.js"></script>
|
HTML代码所需准备
使用Pagination要求的div标签替换原有的页码部分:
旧的代码:
<tfoot> <tr> <td colspan="6" align="center">
<ul class="pagination"> <li class="disabled"><a href="#">上一页</a></li> <li class="active"><a href="#">1 <span class="sr-only">(current)</span> </a> </li> <li><a href="#">2</a> </li> <li><a href="#">3</a> </li> <li><a href="#">4</a> </li> <li><a href="#">5</a> </li> <li><a href="#">下一页</a> </li> </ul>
</td> </tr> </tfoot>
|
新的代码:
<tfoot> <tr> <td colspan="6" align="center"> <div id="Pagination" class="pagination"><!-- 这里显示分页 --></div> </td> </tr> </tfoot>
|
jQuery代码:
<script type="text/javascript"> $(function () { //调用后面声明的函数,对页码导航条进行初始化操作 initPagination(); });
//生成页码导航条 function initPagination() { // 获取总记录数 var totalRecoed = ${requestScope.pageInfo.total }; // 声明一个JSON对象存储Pagination要设置的属性 var properties = { num_edge_entries: 3, //边缘页数 num_display_entries: 6, //主体页数 callback: pageSelectCallBack, // 指定用户点击“翻页”的按钮时跳转页面的回调函数 items_per_page:${requestScope.pageInfo.pageSize }, //每页显示1项 每页要显示的数据的数量 current_page: ${requestScope.pageInfo.pageNum - 1 }, //当前页数pageIndex从0开始 Pageination内部使用pageIndex来管理页码,配置index从0开始,pageNum从1开始,所以要减一 prex_text: "上一页", // 上一页按钮上显示的文本 next_text: "下一页" // 下一页按钮上显示的文本 } // 生成页码导航条 $("#Pagination").pagination(totalRecoed, properties); }
// 用户点击“123”这样的页码时调用这个函数实现页面跳转 function pageSelectCallBack(pageIndex,jQuery) { //根据pageIndex计算得到pageNum var pageNum = pageIndex + 1; //跳转页面 window.location.href = "admin/get/page.html?pageNum="+pageNum"; //由于每一个页码按钮都是超链接,所以我们要在这里取消超链接的默认行为 return false; } </script>
|
修改Pagination的源码
jquery.pagination.js文件中的纰漏:
修改这个文件:
jquery.pagination.js中:
// 所有初始化完成,绘制链接 drawLinks(); // 回调函数 //opts.callback(current_page, this);
|
3 关键词查询
3.1 页面上调整查询表单
<form action="admin/get/page.html" class="form-inline" role="form" style="float:left;"> <div class="form-group has-feedback"> <div class="input-group"> <div class="input-group-addon">查询条件</div> <input name="keyword" class="form-control has-success" type="text" placeholder="请输入查询条件"> </div> </div> <button type="submit" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i> 查询 </button> </form>
|
3.2 在翻页时保持关键词查询条件
admin-page.jsp中:
window.location.href = "admin/get/page.html?pageNum="+pageNum+"&keyword=${param.keyword}";
|
4 单条删除
4.1 目标
4.2 思路
4.3 代码
4.3.1 调整删除的按钮
旧的:
<button type="button" class="btn btn-danger btn-xs"> <i class="glyphicon glyphicon-remove"></i> </button>
|
新的:
<a href="admin/remove/${admin.id }/${requestScope.pageInfo.pageNum}/${param.keyword }
.html" class="btn btn-danger btn-xs"> <i class=" glyphicon glyphicon-remove"></i> </a>
|
4.3.2 AdminHandler.remove()
adminHandler:
@RequestMapping("/admin/remove/{adminId}/{pageNum}/{keyword}.html") public String remove(@PathVariable("adminId") Integer adminId, @PathVariable("pageNum") Integer pageNum, @PathVariable("keyword") String keyword) { // 执行删除 adminService.remove(adminId); // 页面跳转 return "redirect:/admin/get/page.html?pageNum=" + pageNum + "&keyword=" + keyword; }
|
|
4.3.3 AdminService.remove()
adminService:
void remove(Integer adminId);
|
adminServiceImpl:
@Override public void remove(Integer adminId) { adminMapper.deleteByPrimaryKey(adminId); }
|
5 新增
5.1 目标
5.2 思路
5.3 在t_admin表中给账号添加唯一约束
5.4 修改“新增”按钮
新的:
<a style="float: right;" href="admin/to/add/page.html" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> 新增
|
5.5 配置view-controller
<mvc:view-controller path="/admin/to/add/page.html" view-name="admin-add"/>
|
5.6 准备表单页面
<%-- Created by IntelliJ IDEA. User: Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <ol class="breadcrumb"> <li><a href="/admin/to/login/page.html">首页</a></li> <li><a href="/admin/get/page.html">数据列表</a></li> <li class="active">新增</li> </ol> <div class="panel panel-default"> <div class="panel-heading">表单数据<div style="float:right;cursor:pointer;" data-toggle="modal" data-target="#myModal"><i class="glyphicon glyphicon-question-sign"></i></div></div> <div class="panel-body"> <form action="admin/save.html" method="post" role="form"> <p>${requestScope.exception.message }</p> <div class="form-group"> <label for="exampleInputPassword1">登陆账号</label> <input name="loginAcct" type="text" class="form-control" id="exampleInputPassword1" placeholder="请输入登陆账号"> </div> <div class="form-group"> <label for="exampleInputPassword1">登陆密码</label> <input name="userPswd" type="text" class="form-control" id="exampleInputPassword2" placeholder="请输入登陆密码"> </div> <div class="form-group"> <label for="exampleInputPassword1">用户名称</label> <input name="userName" type="text" class="form-control" id="exampleInputPassword3" placeholder="请输入用户名称"> </div> <div class="form-group"> <label for="exampleInputEmail1">邮箱地址</label> <input name="email" type="email" class="form-control" id="exampleInputEmail1" placeholder="请输入邮箱地址"> <p class="help-block label label-warning">请输入合法的邮箱地址, 格式为: xxxx@xxxx.com</p> </div> <button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-plus"></i> 新增</button> <button type="reset" class="btn btn-danger"><i class="glyphicon glyphicon-refresh"></i> 重置</button> </form> </div> </div> </div> </div> </div> </body> </html>
|
5.7 handler方法
@RequestMapping("/admin/save.html") public String save(Admin admin) {
adminService.saveAdmin(admin);
return "redirect:/admin/get/page.html?pageNum=" + Integer.MAX_VALUE; }
|
5.8 Service方法
AdminService:
void saveAdmin(Admin admin);
|
AdminServiceImpl:
@Override public void saveAdmin(Admin admin) {
// 1.密码加密 String userPswd = admin.getUserPswd(); userPswd = CrowdUtil.md5(userPswd); admin.setUserPswd(userPswd); // 2.生成创建时间 Date date = new Date(); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String creatTime = format.format(date); admin.setCreateTime(creatTime); // 3.执行保存 try{ adminMapper.insert(admin); }catch (Exception e) { e.printStackTrace(); logger.info("异常全类名=" + e.getClass().getName());
if (e instanceof DuplicateKeyException) { throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE); } } }
|
创建 “账号重复” 异常类 LoginAcctAlreadyInUseException:
LoginAcctAlreadyInUseException:
package org.example.exception; /* * 保存或更新admin时如果检测到账号重复抛出这个异常 * */ public class LoginAcctAlreadyInUseException extends RuntimeException {
private static final long serialVersionUID = 7713129202396392027L;
public LoginAcctAlreadyInUseException() { super(); }
public LoginAcctAlreadyInUseException(String s) { super(s); }
public LoginAcctAlreadyInUseException(String s, Throwable throwable) { super(s, throwable); }
public LoginAcctAlreadyInUseException(Throwable throwable) { super(throwable); }
protected LoginAcctAlreadyInUseException(String s, Throwable throwable, boolean b, boolean b1) { super(s, throwable, b, b1); } }
|
CrowdExceptionResolver:
@ExceptionHandler(value = LoginAcctAlreadyInUseException.class) public ModelAndView resolveLoginAcctAlreadyInUseException( LoginFailedException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "admin-add";
return commonResolve(viewName, exception, request, response); }
|
6 更新
6.1 目标
6.2 思路
6.3 代码:回显表单
6.3.1 调整铅笔按钮
旧的: <button type="button" class="btn btn-primary btn-xs"> <i class="glyphicon glyphicon-pencil"></i> </button>
新的:
<a href="admin/to/edit/page.html?adminId=${admin.id }&pageNum=${requestScope.pageInfo.pageNum }&keyword=${param.keyword }" class="btn btn-primary btn-xs"> <i class=" glyphicon glyphicon-pencil"></i> </a>
|
6.3.2 handler方法
@RequestMapping("admin/to/edit/page.html") public String toEditPage(@RequestParam("adminId") Integer adminId, ModelMap modelMap) { // 1.根据adminId查询Admin对象 Admin admin = adminService.getAdminById(adminId); // 2.将Admin对象存入模型 modelMap.addAttribute("admin", admin); return "admin-edit"; }
|
6.3.3 Service方法
AdminService:
Admin getAdminById(Integer adminId);
|
AdminServiceImpl:
@Override public Admin getAdminById(Integer adminId) { return adminMapper.selectByPrimaryKey(adminId); }
|
6.3.4 整理表单页面
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <ol class="breadcrumb"> <li><a href="/admin/to/login/page.html">首页</a></li> <li><a href="/admin/get/page.html">数据列表</a></li> <li class="active">更新</li> </ol> <div class="panel panel-default"> <div class="panel-heading">表单数据<div style="float:right;cursor:pointer;" data-toggle="modal" data-target="#myModal"><i class="glyphicon glyphicon-question-sign"></i></div></div> <div class="panel-body"> <form action="admin/update.html" method="post" role="form"> <input type="hidden" name="id" value="${requestScope.admin.id}"> <input type="hidden" name="pageNum" value="${param.pageNum }"> <input type="hidden" name="keyword" value="${param.keyword }"> <p>${requestScope.exception.message }</p> <div class="form-group"> <label for="exampleInputPassword1">登陆账号</label> <input value="${requestScope.admin.loginAcct }" name="loginAcct" type="text" class="form-control" id="exampleInputPassword1" placeholder="请输入登陆账号"> </div> <div class="form-group"> <label for="exampleInputPassword1">用户名称</label> <input value="${requestScope.admin.userName }" name="userName" type="text" class="form-control" id="exampleInputPassword3" placeholder="请输入用户名称"> </div> <div class="form-group"> <label for="exampleInputEmail1">邮箱地址</label> <input value="${requestScope.admin.email }" name="email" type="email" class="form-control" id="exampleInputEmail1" placeholder="请输入邮箱地址"> <p class="help-block label label-warning">请输入合法的邮箱地址, 格式为: xxxx@xxxx.com</p> </div> <button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-edit"></i> 更新</button> <button type="reset" class="btn btn-danger"><i class="glyphicon glyphicon-refresh"></i> 重置</button> </form> </div> </div> </div> </div> </div> </body> </html>
|
6.4 代码:执行更新
6.4.1 handler方法
@RequestMapping("admin/update.html") public String update(Admin admin, @RequestParam("pageNum") Integer pageNum, @RequestParam("keyword") String keyword){ adminService.update(admin); return "redirect:/admin/get/page.html?pageNum=" + pageNum + "&keyword=" + keyword; }
|
6.4.2 Service方法
AdminService:
void update(Admin admin);
|
AdminServiceImpl:
@Override public void update(Admin admin) { // "Selective" 表示有选择的更新,对于null值的字段不更新 try { adminMapper.updateByPrimaryKeySelective(admin); } catch (Exception e) { e.printStackTrace();
if (e instanceof DuplicateKeyException) { throw new LoginAcctAlreadyInUseForUpdateException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE); } } }
|
6.4.3 声明自定义异常类
package org.example.exception; /* * 保存或更新admin时如果检测到账号重复抛出这个异常 * */ public class LoginAcctAlreadyInUseForUpdateException extends RuntimeException {
private static final long serialVersionUID = 7713129202396392027L;
public LoginAcctAlreadyInUseForUpdateException() { super(); }
public LoginAcctAlreadyInUseForUpdateException(String s) { super(s); }
public LoginAcctAlreadyInUseForUpdateException(String s, Throwable throwable) { super(s, throwable); }
public LoginAcctAlreadyInUseForUpdateException(Throwable throwable) { super(throwable); }
protected LoginAcctAlreadyInUseForUpdateException(String s, Throwable throwable, boolean b, boolean b1) { super(s, throwable, b, b1); } }
|
6.4.4 在异常处理器中声明对应的处理方法
@ExceptionHandler(value = LoginAcctAlreadyInUseForUpdateException.class) public ModelAndView resolveLoginAcctAlreadyInUseForUpdateException( LoginFailedException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
String viewName = "system-error";
return commonResolve(viewName, exception, request, response); }
|
06-后台管理系统-RBAC权限控制模型
-Ajax的同步和异步请求
1 异步的工作方式
1.1 图解
1.2 代码
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %> <script type="text/javascript"> $(function () { $("#asyncBtn").click(function () {
console.log("ajax函数之前"); $.ajax({ "url": "test/ajax/async.html", "type": "post", "dataType": "text", "success": function (response) { // success是接收到服务器端响应后执行 console.log("ajax函数内部的success函数" + response); } }); // 在$.ajax()执行完成后执行,不等待success()函数 console.log("ajax函数之后"); }); }) </script> <body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <button id="asyncBtn">发送Ajax请求</button> </div> </div> </div> </body> </html>
|
1.3 打印效果
2 同步的工作方式
2.1 图解
2.2 代码
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %> <script type="text/javascript"> $(function () { $("#asyncBtn").click(function () {
console.log("ajax函数之前"); $.ajax({ "url": "test/ajax/async.html", "type": "post", "dataType": "text", "async": false, // 关闭异步工作模型,使用同步工作方式,此时,所有操作在同一个线程内按顺序完成 "success": function (response) { // success是接收到服务器端响应后执行 console.log("ajax函数内部的success函数" + response); } });
// 在$.ajax()执行完成后执行,不等待success()函数 console.log("ajax函数之后");
}); }) </script> <body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <button id="asyncBtn">发送Ajax请求</button> </div> </div> </div> </body> </html>
|
2.3 打印效果
3 本质
07-后台管理系统-角色维护
1 角色分页操作
1.1 目标
将角色数据进行分页显示。
1.2 思路
1.3 代码:后端
1.3.1 创建数据库表
1.3.2 逆向生成资源
<table tableName="t_role" domainObjectName="Role" />
|
操作:
生成资源后各归其位。
1.3.3 SQL语句
<select id="selectRoleByKeyword" resultMap="BaseResultMap"> select id, name from t_role where name like concat("%",#{keyword},"%") </select>
|
1.3.4 RoleMapper接口
List<Role> selectRoleByKeyword(String keyword);
|
1.3.5 RoleService 接口和实现
RoleService:
package org.example.service.api;
import com.github.pagehelper.PageInfo; import org.example.entity.Role;
/** * @author 123 * date 2023-01-28 */ public interface RoleService {
PageInfo<Role> getPageInfo(Integer pageNum, Integer pageSize, String keyw);
}
|
RoleServiceImpl:
package org.example.service.impl;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.example.entity.Role; import org.example.mapper.RoleMapper; import org.example.service.api.RoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
import java.util.List;
/** * @author 123 * date 2023-01-28 */ @Service public class RoleServiceImpl implements RoleService {
@Autowired private RoleMapper roleMapper;
@Override public PageInfo<Role> getPageInfo(Integer pageNum, Integer pageSize, String keyword) {
// 1.开启分页功能 PageHelper.startPage(pageNum, pageSize);
// 2.执行查询 List<Role> roleList = roleMapper.selectRoleByKeyword(keyword);
// 3.封装为PageInfo对象返回 return new PageInfo<>(roleList); } }
|
1.3.6 RoleHandler
package org.example.mvc.handler;
import com.github.pagehelper.PageInfo; import org.example.entity.Role; import org.example.service.api.RoleService; import org.example.util.ResultEntity; 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;
/** * @author 123 * date 2023-01-28 */ @Controller public class RoleHandler {
@Autowired private RoleService roleService;
@RequestMapping("/role/get/page/info.json") public ResultEntity<PageInfo<Role>> getPageInfo( @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize, @RequestParam(value = "keyword", defaultValue = "") String keyword ) {
// 调用Service方法获取分页数据 PageInfo<Role> pageInfo = roleService.getPageInfo(pageNum, pageSize, keyword);
// 封装到ResultEntity对象中返回(如果上面的操作抛出异常,交给异常映射机制处理) return ResultEntity.successWithData(pageInfo); }
}
|
1.4 代码:过渡
1.4.1 配置 view-controller
<mvc:view-controller path="/role/to/page.html" view-name="admin-add"/>
|
1.4.2 待完善role-page.jsp
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><i class="glyphicon glyphicon-th"></i> 数据列表</h3> </div> <div class="panel-body"> <form class="form-inline" role="form" style="float:left;"> <div class="form-group has-feedback"> <div class="input-group"> <div class="input-group-addon">查询条件</div> <input id="keywordInput" class="form-control has-success" type="text" placeholder="请输入查询条件"> </div> </div> <button id="searchBtn" type="button" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i> 查询 </button> </form> <button id="batchRemoveBtn" type="button" class="btn btn-danger" style="float:right;margin-left:10px;"><i class=" glyphicon glyphicon-remove"></i> 删除 </button> <button id="showModelBtn" type="button" class="btn btn-primary" style="float:right;"><i class="glyphicon glyphicon-plus"></i> 新增 </button> <br> <hr style="clear:both;"> <div class="table-responsive"> <table class="table table-bordered"> <thead> <tr> <th width="30">#</th> <th width="30"><input
type="checkbox"></th> <th>名称</th> <th width="100">操作</th> </tr> </thead> <tbody id="rolePageBody"> </tbody> <tfoot> <tr> <td colspan="6" align="center"> <div id="Pagination" class="pagination"><!-- 这里显示分页 --></div> </td> </tr> </tfoot> </table> </div> </div> </div> </div> </div> </div> </body> </html>
|
1.4.3 修改 “角色维护” 超链接
<li style="height:30px;"> <a href="role/to/page.html"><i class="glyphicon glyphicon-king"></i> 角色维护</a> </li>
|
1.5 代码:前端
1.5.1 初始化数据
<script type="text/javascript">
$(function () {
// 1.为分页操作准备初始化数据 window.pageNum = 1; window.pageSize = 5; window.keyword = "";
})
</script>
|
1.5.2 创建外部JavaScript文件
在role-page.jsp引入外部JavaScript文件my-role.js:
<script type="text/javascript" src="crowd/my-role.js"></script>
|
编写my-role.js文件(部分):
// 执行分页,生成页面效果 任何时候调用这个函数都会重新加载页面 function generatePage() { // 1.获取分页数据 var pageInfoRemote = getPageInfoRemote(); // 2.填充表格 fillTableBody(pageInfoRemote); }
// 远程访问服务器端程序获取pageInfo数据 function getPageInfoRemote() { var ajaxRs = $.ajax({ "url": "role/get/page/info.json", "type": "post", "data": { "pageNum": window.pageNum, "pageSize": window.pageSize, "keyword": window.keyword }, "async": false, "dataType": "json" }); var statusCode = ajaxRs.status; // 发生错误 if (statusCode != 200) { layer.msg("服务器端程序调用失败!响应状态码是=" + statusCode + "说明信息=" + ajaxRs.statusText); return null; } // 如果响应状态码是200表示请求成功 var resultEntity = ajaxRs.responseJSON; // 从resultEntity中获取result属性 var result = resultEntity.result; // 判断result是否成功 if (result == "FAILED") { layer.msg(resultEntity.message); return null; } // 确认result为成功后获取pageInfo var pageInfo = resultEntity.data; // 返回pageInfo return pageInfo; }
// 填充表格 function fillTableBody(pageInfo) { // 清除tbody中的旧的数据 $("#rolePageBody").empty(); // 为了搜索没有结果时不显示页码 $("#Pagination").empty(); // 判断pageInfo是否有效 if (pageInfo == null || pageInfo == undefined || pageInfo.list.length == 0) { $("#rolePageBody").append("<tr><td colspan='4'>抱歉!没有查询到您要的数据!</td></tr>"); return; } // 使用pageInfo的list属性填充tbody for (var i = 0; i < pageInfo.list.length; i++) { var role = pageInfo.list[i]; var roleId = role.id; var roleName = role.name; var numberTd = "<td>" + (i + 1) + "</td>"; var checkboxTd = "<td><input type='checkbox'></td>" var roleNameTd = "<td>" + roleName + "</td>"
var checkBtn = "<button type='button' class='btn btn-success btn-xs'><i class=' glyphicon glyphicon-check'></i></button>"; var pencilBtn = "<button type='button' class='btn btn-primary btn-xs'><i class='glyphicon glyphicon-pencil'></i></button>"; var removeBtn = "<button type='button' class='btn btn-danger btn-xs'><i class='glyphicon glyphicon-remove'></i></button>"; var buttonTd = "<td>" + checkBtn + " " + pencilBtn + " " + removeBtn + "</td>"; var tr = "<tr>" + numberTd + checkboxTd + roleNameTd + buttonTd + "</tr>"; $("#rolePageBody").append(tr); } // 生成分页导航条 generateNavigator(pageInfo); }
// 生成分页页码导航条 // 生成分页页码导航条 function generateNavigator(pageInfo) { // 获取总记录数 var totalRecord = pageInfo.total; // 声明相关属性 var properties = { "num_edge_entries": 3, "num_display_entries": 5, "callback": paginationCallBack, "items_per_page": pageInfo.pageSize, "current_page": pageInfo.pageNum - 1, "prev_text": "上一页", "next_text": "下一页" } // 调用 pagination()函数 $("#Pagination").pagination(totalRecord, properties); }
// 翻页时的回调函数 function paginationCallBack(pageIndex, jQuery) { // 修改 window 对象的 pageNum 属性 window.pageNum = pageIndex + 1; // 调用分页函数 generatePage(); // 取消页码超链接的默认行为 return false;
|
报错:
解决:
在role-page.jsp中添加环境:
<link rel="stylesheet" href="css/pagination.css"/> <script type="text/javascript" src="jquery/jquery.pagination.js"></script>
|
2 角色查询操作
2.1 目标
把页面上的“查询”表单和已经封装好的执行分页的函数连接起来即可。
2.2 思路
2.3 代码
2.3.1 HTML中标记id
<form class="form-inline" role="form" style="float:left;"> <div class="form-group has-feedback"> <div class="input-group"> <div class="input-group-addon">查询条件</div> <input id="keywordInput" class="form-control has-success" type="text" placeholder="请输入查询条件"> </div> </div> <button id="searchBtn" type="button" class="btn btn-warning"><i class="glyphicon glyphicon-search"></i> 查询 </button> </form>
|
2.3.2 jQuery代码
// 3.给查询按钮绑定单击响应函数 $("#searchBtn").click(function () { // 1.获取关键词数据赋值给全局变量 window.keyword = $("#keywordInput").val(); // 2,调用分页函数刷新页面 generatePage(); });
|
3 角色保存操作
3.1 目标
3.2 思路
3.3 代码:页面引入模态框
3.3.1 创建JSP文件
3.3.2 加入模态框HTML代码 model-role-add.jsp
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/31 Time: 13:27 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <div id="addModal" class="modal fade" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">尚筹网系统弹窗</h4> </div> <div class="modal-body"> <form action="admin/do/login.html" method="post" class="form-signin" role="form"> <h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 管理员登录</h2> <div class="form-group has-success has-feedback"> <input type="text" name="roleName" class="form-control" placeholder="请输入角色名称" autofocus> </div> </form> </div> <div class="modal-footer"> <button id="saveRoleBtn" type="button" class="btn btn-primary">保存</button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> </body> </html>
|
3.3.3 在role-page.jsp引入上面的文件 model-role-add.jsp
<%@include file="/WEB-INF/model-role-add.jsp" %>
|
3.4 代码:打开模态框
3.4.1 修改新增按钮
<button id="showModelBtn" type="button" class="btn btn-primary" style="float:right;"><i class="glyphicon glyphicon-plus"></i> 新增 </button>
|
3.4.2 给新增按钮绑定单击响应函数
// 4.点击新增按钮打开模态框 $("#showModelBtn").click(function () { $("#addModal").modal("show"); });
|
3.5 代码:执行保存
3.5.1 前端代码
// 5.给新增模态框中的保存按钮绑定单击响应函数 $("#saveRoleBtn").click(function () { // 1.获取用户在文本框中输入的角色名称 // #addModal表示找到整个模态框 // 空格表示在后代元素中继续查找 // [name=roleName]表示匹配name属性等于roleName的元素 var roleName = $.trim($("#addModal [name=roleName]").val());
// 2.发送ajax请求 $.ajax({ "url": "role/save.json", "type": "post", "data": { "name": roleName }, "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { layer.msg("操作成功!");
// 将页码定位到最后一页 window.pageNum = 99999999; } if (result == "FAILED") { layer.msg("操作失败!" + response.message) } // 重新加载分页 generatePage(); }, "error": function (response) { layer.msg(response.status + " " + response.statusText); } }); // 关闭模态框 $("#addModal").modal("hide"); // 清理模态框 $("#addModal [name=roleName]").val("");
//
});
|
3.5.2 后端代码
RoleHandler.class:
@ResponseBody @RequestMapping("/role/save.json") public ResultEntity<String> savaRole(Role role){ roleService.saveRole(role); return ResultEntity.successWithoutData(); }
|
RoleService:
void saveRole(Role role);
|
RoleServiceImpl:
@Override public void saveRole(Role role) { roleMapper.insert(role); }
|
4 角色更新操作
4.1 目标
修改角色信息
4.2 思路
4.3 代码:页面引入模态框
4.3.1 创建JSP文件
4.3.2 加入模态框的HTML代码
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/2/1 Time: 18:09 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <div id="editModal" class="modal fade" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">尚筹网系统弹窗</h4> </div> <div class="modal-body"> <form action="admin/do/login.html" method="post" class="form-signin" role="form"> <h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 管理员登录</h2> <div class="form-group has-success has-feedback"> <input type="text" name="roleName" class="form-control" placeholder="请输入角色名称" autofocus> </div> </form> </div> <div class="modal-footer"> <button id="updateRoleBtn" type="button" class="btn btn-success">更新</button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> </body> </html>
|
4.3.3 在role-page.jsp引入模态框
<%@include file="/WEB-INF/model-role-edit.jsp" %>
|
4.4 代码:打开模态框(回显)
4.4.1 修改 “铅笔” 按钮
// 通过button标签的id属性把roleId值传递到button按钮的单击响应函数中 var checkBtn = "<button id='" + roleId + "' type='button' class='btn btn-success btn-xs checkBtn'><i class=' glyphicon glyphicon-check'></i></button>";
|
4.4.2 给 “铅笔” 按钮绑定单击响应函数
role-page.jsp:
// 6.给页面上的铅笔按钮绑定单击响应函数,目的是打开模态框 // 使用下方第一种绑定的方式(也就是传统的事件绑定方式),出现问题:只在第一个页面有效,翻页后就失效了 /*$(".pencilBtn").click(function () { alert("aaaaa...."); });*/ // 使用jQuery对象的 on() 解决上面问题 // 1.首先找到所有“动态生成“的元素所附着的”静态“元素 // 2.on函数的第一个参数是事件类型,第二个参数是真正要绑定事件的选择器 // 3.第三个参数是响应函数 $("#rolePageBody").on("click", ".pencilBtn", function () { // 打开模态框 $("#editModal").modal("show"); // 获取表格中当前行中的角色名称 var roleName = $(this).parent().prev().text(); // 获取当前角色的 id // 依据是:var pencilBtn = "<button id='"+roleId+"' ……这段代码中我们把 roleId 设置到id 属性了 // 为了让执行更新的按钮能够获取到 roleId 的值,把它放在全局变量上 window.roleId = this.id; // 使用 roleName 的值设置模态框中的文本框 $("#editModal [name=roleName]").val(roleName); });
|
4.5 代码:执行更新
4.5.1 前端
role-page.jsp:
// 7.给更新模态框中的更新按钮绑定单击响应函数 $("#updateRoleBtn").click(function () { // ①从文本框中获取新的角色名称 var roleName = $("#editModal [name=roleName]").val(); // ②发送 Ajax 请求执行更新 $.ajax({ "url": "role/update.json", "type": "post", "data": { "id": window.roleId, "name": roleName }, "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { layer.msg("操作成功!"); // 重新加载分页数据 generatePage(); } if (result == "FAILED") { layer.msg("操作失败!" + response.message); } }, "error": function (response) { layer.msg(response.status + " " + response.statusText); } }); // ③关闭模态框 $("#editModal").modal("hide"); });
|
4.5.2 后端
RoleHandler:
@ResponseBody @RequestMapping("/role/update.json") public ResultEntity<String> updateRole(Role role) { System.out.println(role.toString()); roleService.updateRole(role); return ResultEntity.successWithoutData(); }
|
RoleService:
void updateRole(Role role);
|
RoleServiceImpl:
@Override public void updateRole(Role role) { roleMapper.updateByPrimaryKey(role); }
|
5 角色删除操作
5.1 目标
5.2 思路
5.3 代码:后端
handler方法:
@RequestMapping("/role/remove/by/role/id/array.json") public ResultEntity<String> removeByRoleIdArray(@RequestBody List<Integer> roleIdList){ System.out.println("-------------"); System.out.println(roleIdList.toString()); roleService.removeRole(roleIdList); return ResultEntity.successWithoutData(); }
|
RoleService:
void removeRole(List<Integer> roleIdList);
|
RoleServiceImpl:
@Override public void removeRole(List<Integer> roleIdList) { RoleExample roleExample = new RoleExample(); RoleExample.Criteria criteria = roleExample.createCriteria(); // delete from t_role where id in (5,8,12) criteria.andIdIn(roleIdList); roleMapper.deleteByExample(roleExample); }
|
5.4 代码:前端(公共部分)
5.4.1 声明函数用于打开模态框
// 声明专门的函数显示确认模态框 function showConfirmModel(roleArray) { // 打开模态框 $("#confirmModal").modal("show"); // 清除旧数据 $("#roleNameDiv").empty(); // 在全局变量范围内创建数组来存放角色id window.roleIdArray = []; // 遍历roleArray数组 for (var i = 0; i < roleArray.length; i++) { var role = roleArray[i]; var roleName = role.roleName; $("#roleNameDiv").append(roleName + "<br />"); var roleId = role.roleId; window.roleIdArray.push(roleId); } }
|
5.4.2 给确认模态框中的 “确认删除” 按钮绑定单击响应函数
role-page.jsp:
// 8.点击确认模态框中的确认删除按钮执行删除 $("#removeRoleBtn").click(function () {
var requestBody = JSON.stringify(window.roleIdArray);
$.ajax({ "url": "role/remove/by/role/id/array.json", "type": "post", "data": requestBody, "contentType": "application/json;charset=UTF-8", "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { layer.msg("操作成功!"); // 重新加载分页数据 generatePage(); } if (result == "FAILED") { layer.msg("操作失败!" + response.message); } }, "error": function (response) { layer.msg(response.status + " " + response.statusText); } }); // ③关闭模态框 $("#confirmModal").modal("hide"); });
|
报错(测试单个删除时):400错误
解决:
在 removeByRoleIdArray 方法中添加注解 @ResponseBody,↓
5.5 代码:前端(单条删除)
5.5.1 修改删除按钮
在 fillTableBody(pageInfo) 函数中:
var removeBtn = "<button id='" + roleId + "' type='button' class='btn btn-danger btn-xs removeBtn'><i class='glyphicon glyphicon-remove'></i></button>";
|
5.5.2 绑定单击响应函数
role-page.jsp:
// 9.给单条删除按钮绑定单击响应函数 $("#rolePageBody").on("click", ".removeBtn", function () { //从当前按钮出发获取角色名称 var roleName = $(this).parent().prev().text();
// 创建role对象 var roleArray = [{ roleId: this.id, roleName: roleName, }]; // 调用函数打开模态框 showConfirmModel(roleArray); generatePage(); // });
|
5.6 代码:前端(批量删除)
5.6.1 全选全不选
role-page.jsp:
// 10.给总的checkBox绑定单击响应 $("#summaryBox").click(function () { // 1.获取当前多选框自身的状态 var currentStatus = this.checked; // 2.用当前多选框的状态去设置其他多选框 $('.itemBox').prop("checked", currentStatus); }); // 11.全选、全不选的反向操作 $("#rolePageBody").on("click", ".itemBox", function () { // 获取当前已经选中的.itemBox的数量 var checkedBoxCount = $(".itemBox:checked").length; // 获取全部.itemBox的数量 var totalBoxCount = $(".itemBox").length; // 使用二者的比较结果设置总的checkBox $("#summaryBox").prop("checked", checkedBoxCount == totalBoxCount); });
|
5.6.2 给批量删除按钮绑定单击响应函数
role-page.jsp:
// 12.给批量删除的按钮绑定单击响应函数 $("#batchRemoveBtn").click(function () { // 创建数组存放获取到的对象 var roleArray = []; //遍历当前选中的多选框 $(".itemBox:checked").each(function () { // 使用this引用当前遍历得到的多选框 var roleId = this.id; // 通过DOM操作获取角色名称 var roleName = $(this).parent().next().text(); roleArray.push({ "roleId": roleId, "roleName": roleName }); }); // 检查roleArray的长度是否为0 if (roleArray.length == 0) { layer.msg("请至少选择一个执行删除!"); return; } // 调用函数打开模态框 showConfirmModel(roleArray); });
|
修改(添加id属性):
<button id="batchRemoveBtn" type="button" class="btn btn-danger" style="float:right;margin-left:10px;"><i class=" glyphicon glyphicon-remove"></i> 删除 </button>
|
08-后台管理系统-菜单维护
1 树形结构基础知识介绍
1.1 节点类型
1.2 在数据库表中表示树形结构
1.2.1 创建菜单的数据库表
1.2.2 插入数据
INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (1, NULL, '系统权限菜单', NULL, 'glyphicon\r\nglyphicon-th-list'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (2, 1, '控 制 面 板', 'main.htm', 'glyphicon glyphicon glyphicon-tasks'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (3, 1, '权限管理', NULL, 'glyphicon glyphicon\r\nglyphicon-tasks'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (4, 3, ' 用 户 维 护 ', 'user/index.htm', 'glyphicon\r\nglyphicon-user'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (5, 3, ' 角 色 维 护 ', 'role/index.htm', 'glyphicon\r\nglyphicon-king'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (6, 3, ' 菜 单 维 护 ', 'permission/index.htm', 'glyphicon\r\nglyphicon-lock'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (7, 1, ' 业 务 审 核 ', NULL, 'glyphicon\r\nglyphicon-ok'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (8, 7, '实名认证审核', 'auth_cert/index.htm', 'glyphicon\r\nglyphicon-check'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (9, 7, ' 广 告 审 核 ', 'auth_adv/index.htm', 'glyphicon\r\nglyphicon-check'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (10, 7, ' 项 目 审 核 ', 'auth_project/index.htm', 'glyphicon\r\nglyphicon-check'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (11, 1, ' 业 务 管 理 ', NULL, 'glyphicon\r\nglyphicon-th-large'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (12, 11, ' 资 质 维 护 ', 'cert/index.htm', 'glyphicon\r\nglyphicon-picture'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (13, 11, ' 分 类 管 理 ', 'certtype/index.htm', 'glyphicon\r\nglyphicon-equalizer'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (14, 11, ' 流 程 管 理 ', 'process/index.htm', 'glyphicon\r\nglyphicon-random'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (15, 11, ' 广 告 管 理 ', 'advert/index.htm', 'glyphicon\r\nglyphicon-hdd'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (16, 11, ' 消 息 模 板 ', 'message/index.htm', 'glyphicon\r\nglyphicon-comment'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (17, 11, ' 项 目 分 类 ', 'projectType/index.htm', 'glyphicon\r\nglyphicon-list'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (18, 11, ' 项 目 标 签 ', 'tag/index.htm', 'glyphicon\r\nglyphicon-tags'); INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (19, 1, ' 参 数 管 理 ', 'param/index.htm', 'glyphicon\r\nglyphicon-list-alt');
|
1.2.3 关联方式
1.3 在Java类中表示树形结构
1.3.1 基本方式
1.3.2 为了配合zTree所需要添加的属性
1.4 按钮增删改查的规则
2 菜单维护:页面显示树形结构
2.1 目标
2.2 思路
2.3 代码:逆向工程
<table tableName="t_menu" domainObjectName="Menu" />
|
逆向生成的Menu实体类需要做一些调整:
package org.example.entity;
import java.util.ArrayList; import java.util.List;
public class Menu { // 主键 private Integer id;
// 父节点 private Integer pid;
// 节点名称 private String name;
// 节点附带的URL地址,是将来点击菜单项时要跳转的地址 private String url;
// 节点图标的样式 private String icon;
// 存储子节点的集合,初始化是为了避免空指针异常 private List<Menu> children = new ArrayList<>();
// 控制节点是否默认为打开状态,设置为true表示为默认打开 private Boolean open = true;
@Override public String toString() { return "Menu{" + "id=" + id + ", pid=" + pid + ", name='" + name + '\'' + ", url='" + url + '\'' + ", icon='" + icon + '\'' + ", children=" + children + ", open=" + open + '}'; }
public Menu(Integer id, Integer pid, String name, String url, String icon, List<Menu> children, Boolean open) { this.id = id; this.pid = pid; this.name = name; this.url = url; this.icon = icon; this.children = children; this.open = open; }
public Menu() { }
public List<Menu> getChildren() { return children; }
public void setChildren(List<Menu> children) { this.children = children; }
public Boolean getOpen() { return open; }
public void setOpen(Boolean open) { this.open = open; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public Integer getPid() { return pid; }
public void setPid(Integer pid) { this.pid = pid; }
public String getName() { return name; }
public void setName(String name) { this.name = name == null ? null : name.trim(); }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url == null ? null : url.trim(); }
public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon == null ? null : icon.trim(); } }
|
最后各归各位。
2.4 代码:将数据在Java代码中组装成树形结构
2.4.1 service方法
package org.example.service.api;
import org.example.entity.Menu;
import java.util.List;
/** * @author 123 * date 2023-02-02 */ public interface MenuService {
List<Menu> getAll();
}
|
package org.example.service.impl;
import org.example.entity.Menu; import org.example.entity.MenuExample; import org.example.mapper.MenuMapper; import org.example.service.api.MenuService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
import java.util.List;
/** * @author 123 * date 2023-02-02 */ @Service public class MenuServiceImpl implements MenuService {
@Autowired private MenuMapper menuMapper;
@Override public List<Menu> getAll() { return menuMapper.selectByExample(new MenuExample()); } }
|
2.4.2 handler方法
package org.example.mvc.handler;
import org.example.entity.Menu; import org.example.service.api.MenuService; import org.example.util.ResultEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap; import java.util.List; import java.util.Map;
/** * @author 123 * date 2023-02-02 */ @RestController public class MenuHandler {
@Autowired private MenuService menuService;
@RequestMapping("/menu/get/whole/tree.json") public ResultEntity<Menu> getWholeTreeNew() { // 1.查询全部的 Menu 对象 List<Menu> menuList = menuService.getAll(); // 2.声明一个变量用来存储找到的根节点 Menu root = null; // 3.创建 Map 对象用来存储 id 和 Menu 对象的对应关系便于查找父节点 Map<Integer, Menu> menuMap = new HashMap<>(); // 4.遍历 menuList 填充 menuMap for (Menu menu : menuList) { Integer id = menu.getId(); menuMap.put(id, menu); } // 5.再次遍历 menuList 查找根节点、组装父子节点 for (Menu menu : menuList) { // 6.获取当前 menu 对象的 pid 属性值 Integer pid = menu.getPid(); // 7.如果 pid 为 null,判定为根节点 if(pid == null) { root = menu; // 8.如果当前节点是根节点,那么肯定没有父节点,不必继续执行 continue ; } // 9.如果 pid 不为 null,说明当前节点有父节点,那么可以根据 pid 到 menuMap 中查找对应的 Menu 对象 Menu father = menuMap.get(pid); // 10.将当前节点存入父节点的 children 集合 father.getChildren().add(menu); } // 11.经过上面的运算,根节点包含了整个树形结构,返回根节点就是返回整个树 return ResultEntity.successWithData(root); }
}
|
2.5 代码:跳转页面
配置view-controller:
<mvc:view-controller path="/menu/to/page.html" view-name="menu-page"/>
|
新建menu-page.jsp:
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="panel panel-default"> <div class="panel-heading"> <i class="glyphicon glyphicon-th-list"></i> 权限菜单列表 <div style="float:right;cursor:pointer;" data-toggle="modal" data-target="#myModal"> <i class="glyphicon glyphicon-question-sign"></i> </div> </div> <div class="panel-body"> <ul id="treeDemo" class="ztree"> </ul> </div> </div> </div> </div> </div> </body> </html>
|
2.6 代码:引入zTree环境
menu-page.jsp页面引入环境:
<link rel="stylesheet" href="ztree/zTreeStyle.css"/> <script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script>
|
2.7 代码:页面上使用zTree初步显示树形结构(假数据)
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %> <link rel="stylesheet" href="ztree/zTreeStyle.css"/> <script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script> <script type="text/javascript"> $(function () {
// 1 创建JSON对象用于存储对zTree所做的设置 var setting = { };
// 2 准备生成树形结构的JSON数据 var zNodes =[ { name:"父节点1 - 展开", open:true, children: [ { name:"父节点11 - 折叠", children: [ { name:"叶子节点111"}, { name:"叶子节点112"}, { name:"叶子节点113"}, { name:"叶子节点114"} ]}, { name:"父节点12 - 折叠", children: [ { name:"叶子节点121"}, { name:"叶子节点122"}, { name:"叶子节点123"}, { name:"叶子节点124"} ]}, { name:"父节点13 - 没有子节点", isParent:true} ]}, { name:"父节点2 - 折叠", children: [ { name:"父节点21 - 展开", open:true, children: [ { name:"叶子节点211"}, { name:"叶子节点212"}, { name:"叶子节点213"}, { name:"叶子节点214"} ]}, { name:"父节点22 - 折叠", children: [ { name:"叶子节点221"}, { name:"叶子节点222"}, { name:"叶子节点223"}, { name:"叶子节点224"} ]}, { name:"父节点23 - 折叠", children: [ { name:"叶子节点231"}, { name:"叶子节点232"}, { name:"叶子节点233"}, { name:"叶子节点234"} ]} ]}, { name:"父节点3 - 没有子节点", isParent:true}
];
// 3 初始化树形结构 $.fn.zTree.init($("#treeDemo"), setting, zNodes);
}); </script>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="panel panel-default"> <div class="panel-heading"> <i class="glyphicon glyphicon-th-list"></i> 权限菜单列表 <div style="float:right;cursor:pointer;" data-toggle="modal" data-target="#myModal"> <i class="glyphicon glyphicon-question-sign"></i> </div> </div> <div class="panel-body"> <%--这个ul标签是zTree动态生成的节点所依附的静态节点--%> <ul id="treeDemo" class="ztree"> </ul> </div> </div> </div> </div> </div> </body> </html>
|
2.8 代码:在页面上使用真实数据显示树形结构
menu-page.jsp:
$(function () {
// 1 准备生成树形结构的JSON数据,数据的来源是发送Ajax请求得到 $.ajax({ "url": "menu/get/whole/tree.json", "type": "post", "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { // 2 创建JSON对象用于存储对zTree所做的设置 var setting = {};
// 3 从响应体中获取用来生成树形结构的JSON数据 var zNodes = response.data;
// 4 初始化树形结构 $.fn.zTree.init($("#treeDemo"), setting, zNodes); } if (result == "FAILED") { layer.msg(response.message); } } });
});
|
测试 “在页面上使用真实数据显示树形结构” 报错:404
解决:
MuneHandler.class 中的@Controller改为@RestController注解
2.9 代码:修改默认图标为真实图标
menu-page.jsp:
$(function () {
// 1 准备生成树形结构的JSON数据,数据的来源是发送Ajax请求得到 $.ajax({ "url": "menu/get/whole/tree.json", "type": "post", "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { // 2 创建JSON对象用于存储对zTree所做的设置 var setting = { "view": { "addDiyDom": myAddDiyDom } };
// 3 从响应体中获取用来生成树形结构的JSON数据 var zNodes = response.data;
// 4 初始化树形结构 $.fn.zTree.init($("#treeDemo"), setting, zNodes); } if (result == "FAILED") { layer.msg(response.message); } } });
});
|
my-menu.js:
function myAddDiyDom(treeId, treeNode) {
// treeId是整个树形结构附着的ul标签的id // console.log("treeId=" + treeId);
// 当前树形节点的全部的数据,包括从后端查询得到的Menu对象的全部属性 // console.log(treeNode);
// zTree生成id的规则 treeDome_7_ico // 解析:ul标签的id_当前节点的序号_功能 // 提示:“ul标签的id_当前节点的序号” 部分可以通过访问treeNode的tId属性得到 // 根据id的生成规则拼凑出来span标签的id var spanId = treeNode.tId + "_ico";
// 根据控制图标的span标签的id找到这个span标签 // 删除旧的class // 添加新的class $("#" + spanId) .removeClass() .addClass(treeNode.icon);
}
|
2.10 代码:实现 “点了不跑”
menu-page.jsp:
$(function () {
// 1 准备生成树形结构的JSON数据,数据的来源是发送Ajax请求得到 $.ajax({ "url": "menu/get/whole/tree.json", "type": "post", "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { // 2 创建JSON对象用于存储对zTree所做的设置 var setting = { "view": { "addDiyDom": myAddDiyDom }, "data": { "key": { "url": "maomi" } } };
// 3 从响应体中获取用来生成树形结构的JSON数据 var zNodes = response.data;
// 4 初始化树形结构 $.fn.zTree.init($("#treeDemo"), setting, zNodes); } if (result == "FAILED") { layer.msg(response.message); } } });
});
|
2.11 代码:显示按钮组
2.11.1 思路和步骤
2.11.2 myRemoveHoverDom(treeId, treeNode) 函数
// 在鼠标移开节点范围时删除按钮组 function myRemoveHoverDom(treeId, treeNode) { // 拼接按钮组的id var btnGroupId = treeNode.tId + "_btnGrp"; //移除对应的元素 $("#" + btnGroupId).remove(); }
|
2.11.3 myAddHoverDom(treeId, treeNode) 函数
// 在鼠标移入节点范围时添加按钮组 function myAddHoverDom(treeId, treeNode) { // 按钮组的标签结构:<span><a><i></i></a><a><i></i></a></span> // 按钮组出现的位置:节点中 treeDemo_n_a 超链接的后面 // 为了在需要移除按钮组的时候能够精确定位到按钮组所在 span,需要给 span 设置有规律的 id var btnGroupId = treeNode.tId + "_btnGrp"; // 判断一下以前是否已经添加了按钮组 if($("#"+btnGroupId).length > 0) { return ; } // 准备各个按钮的HTML标签 var addBtn = "<a id='" + treeNode.id + "' class='addBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' title='添加子节点'> <i class='fa fa-fw fa-plus rbg '></i></a>"; var removeBtn = "<a id='" + treeNode.id + "' class='removeBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' title=' 删 除 节 点 '> <i class='fa fa-fw fa-times rbg '></i></a>"; var editBtn = "<a id='" + treeNode.id + "' class='editBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' title=' 修 改 节 点 '> <i class='fa fa-fw fa-edit rbg '></i></a>";
// 获取当前结点的级别 var level = treeNode.level; // 声明变量存储拼装好的按钮代码 var btnHTML = ""; // 判断当前节点的级别 if(level == 0) { // 级别为 0 时是根节点,只能添加子节点 btnHTML = addBtn; } if(level == 1) { // 级别为 1 时是分支节点,可以添加子节点、修改 btnHTML = addBtn + " " + editBtn; // 获取当前节点的子节点数量 var length = treeNode.children.length; // 如果没有子节点,可以删除 if(length == 0) { btnHTML = btnHTML + " " + removeBtn; } } if(level == 2) { // 级别为 2 时是叶子节点,可以修改、删除 btnHTML = editBtn + " " + removeBtn; } // 找到附着按钮组的超链接 var anchorId = treeNode.tId + "_a"; //执行在超链接后面附加span元素的操作 $("#" + anchorId).after("<span id='" + btnGroupId + "'>" + btnHTML + "</span>");
}
|
2.12 代码:把生成树形结构的代码封装到函数
function generateTree() { // 1.准备生成树形结构的JSON数据,数据的来源是发送ajax请求得到的 $.ajax({ "url": "menu/get/whole/tree.json", "type": "post", "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { // 2.创建JSON对象用于存储对zTree所做的设置 var setting = { "view":{ "addDiyDom": myAddDiyDom, "addHoverDom":myAddHoverDom, "removeHoverDom":myRemoveHoverDom }, "data":{ "key":{ "url":"maomi" } } }; // 3.从响应体获取数据 var zNodes = response.data; // 4.初始化树形结构 $.fn.zTree.init($("#treeDemo"), setting, zNodes); } if (result == "FAILED") { layer.msg(response.message); } } }); }
|
menu-page.jsp页面上调用函数即可:
$(function () {
// 调用专门封装好的函数初始化树形结构 generatePage();
});
|
3 菜单维护:添加子节点
3.1 目标
3.2 思路
3.3 代码:前端
3.3.1 给 “+” 按钮添加class值
var addBtn = "<a id='" + treeNode.id + "' class='addBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' title='添加子节点'> <i class='fa fa-fw fa-plus rbg '></i></a>";
|
顺便把另外两个按钮也加上:
var removeBtn = "<a id='" + treeNode.id + "' class='removeBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' title=' 删 除 节 点 '> <i class='fa fa-fw fa-times rbg '></i></a>"; var editBtn = "<a id='" + treeNode.id + "' class='editBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' title=' 修 改 节 点 '> <i class='fa fa-fw fa-edit rbg '></i></a>";
|
3.3.2 给 “+” 按钮绑定单击响应函数
menu-page.jsp:
// 给添加子结点按钮绑定单击响应函数 $("#treeDemo").on("click", ".addBtn", function () { // 将当前结点的id,作为新节点的pid保存到全局变量 window.pid = this.id; // 打开模态框,将当前结点的id,作为新节点的pid $("#menuAddModal").modal("show"); return false; });
|
3.3.3 给添加子结点的模态框中的保存按钮绑定单击响应函数
menu-page.jsp:
// 给添加子结点的模态框中的保存按钮绑定单击响应函数 $("#menuSaveBtn").click(function () { // 收集表单项中用户输入的数据 var name = $.trim($("#menuAddModal [name=name]").val()); var url = $.trim($("#menuAddModal [name=url]").val()); // 单选按钮要定位到“被选中”的那一个 var icon = $("#menuAddModal [name=icon]:checked").val(); // 发送ajax请求 $.ajax({ "url": "menu/save.json", "type": "post", "data": { "pid": window.pid, "name": name, "url": url, "icon": icon }, "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { layer.msg("操作成功!"); // 重新加载树形结构 generateTree(); } if (result == "FAILED") { layer.msg("操作失败!" + response.message); } }, "error": function (response) { layer.msg(response.status + " " + response.statusText); } }); $("#menuAddModal").modal("hide"); // 清空表单 $("#menuResetBtn").click(); });
|
3.4 代码:后端
handler方法:
@RequestMapping("/menu/save.json") public ResultEntity<String> saveMenu(Menu menu){ menuService.saveMenu(menu); return ResultEntity.successWithoutData(); }
|
menuService方法:
void saveMenu(Menu menu);
|
menuServiceImpl方法:
@Override public void saveMenu(Menu menu) { menuMapper.insert(menu); }
|
4 菜单维护:更新节点
4.1 目标
4.2 思路
4.3 代码:前端
4.3.1 给 “修改” 按钮绑定单击响应函数
menu-page.jsp:
// 给添加子结点按钮绑定单击响应函数 $("#treeDemo").on("click", ".editBtn", function () { // 将当前节点的 id 保存到全局变量 window.id = this.id; // 打开模态框 $("#menuEditModal").modal("show"); // 获取 zTreeObj 对象 var zTreeObj = $.fn.zTree.getZTreeObj("treeDemo"); // 根据id属性查询节点对象 // 用来搜索节点的属性名 var key = "id"; // 用来搜索节点的属性值 var value = window.id; var currentNode = zTreeObj.getNodeByParam(key, value); // 回显表单数据 $("#menuEditModal [name=name]").val(currentNode.name); $("#menuEditModal [name=url]").val(currentNode.url); // 回显 radio 可以这样理解:被选中的 radio 的 value 属性可以组成一个数组, // 然后再用这个数组设置回 radio,就能够把对应的值选中 $("#menuEditModal [name=icon]").val([currentNode.icon]); return false; });
|
4.3.2 给更新模态框中的更新按钮绑定单击响应函数
menu-page.jsp:
$("#menuEditBtn").click(function () { // 收集表单数据 var name = $("#menuEditModal [name=name]").val(); var url = $("#menuEditModal [name=url]").val(); // 回显 radio 可以这样理解:被选中的 radio 的 value 属性可以组成一个数组, // 然后再用这个数组设置回 radio,就能够把对应的值选中 var icon = $("#menuEditModal [name=icon]:checked").val(); // 发送ajax请求 $.ajax({ "url": "menu/update.json", "type": "post", "data": { "id": window.id, "name": name, "url": url, "icon": icon }, "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { layer.msg("操作成功!"); // 重新加载树形结构 generateTree(); } if (result == "FAILED") { layer.msg("操作失败!" + response.message); } }, "error": function (response) { layer.msg(response.status + " " + response.statusText); } }); $("#menuEditModal").modal("hide"); });
|
4.4 代码:后端
handler代码:
@RequestMapping("/menu/update.json") public ResultEntity<String> updateMenu(Menu menu){ menuService.updateMenu(menu);
return ResultEntity.successWithoutData(); }
|
menuService方法:
void updateMenu(Menu menu);
|
menuServiceImpl方法:
@Override public void updateMenu(Menu menu) { // 由于pid没有传入所以要选择有选择的更新 menuMapper.updateByPrimaryKeySelective(menu); }
|
5 菜单维护:删除节点
5.1 目标
删除当前节点
5.2 思路
5.3 代码:前端
5.3.1 给 “×” 按钮绑定单击响应函数
menu-page.jsp:
$("#treeDemo").on("click",".removeBtn",function () { // 将当前节点的 id 保存到全局变量 window.id = this.id; // 打开模态框 $("#menuConfirmModal").modal("show"); // 获取 zTreeObj 对象 var zTreeObj = $.fn.zTree.getZTreeObj("treeDemo"); // 根据 id 属性查询节点对象 // 用来搜索节点的属性名 var key = "id"; // 用来搜索节点的属性值 var value = window.id; var currentNode = zTreeObj.getNodeByParam(key, value); $("#removeNodeSpan").html(" <i class='"+currentNode.icon+"'></i>"+currentNode.name+""); return false; });
|
5.3.2 给确认模态框中的按钮绑定单击响应函数
menu-page.jsp:
$("#confirmBtn").click(function(){ $.ajax({ "url":"menu/remove.json", "type":"post", "data":{ "id":window.id }, "dataType":"json", "success":function(response){ var result = response.result; if(result == "SUCCESS") { layer.msg("操作成功!"); // 重新加载树形结构,注意:要在确认服务器端完成保存操作后再刷新 // 否则有可能刷新不到最新的数据,因为这里是异步的 generateTree(); } if(result == "FAILED") { layer.msg("操作失败!"+response.message); } }, "error":function(response){ layer.msg(response.status+" "+response.statusText); } }); // 关闭模态框 $("#menuConfirmModal").modal("hide"); });
|
5.4 代码:后端
handler方法:
@RequestMapping("/menu/remove.json") public ResultEntity<String> removeMenu(@RequestParam("id") Integer id) { menuService.removeMenu(id); return ResultEntity.successWithoutData(); }
|
menuService方法:
void removeMenu(Integer id);
|
menuServiceImpl方法:
@Override public void removeMenu(Integer id) { menuMapper.deleteByPrimaryKey(id); }
|
- @RestController = @Controller + @ResponseBody
MenuHandler.java、RoleHandler.java都进行 注解的修改。
09-后台管理系统-分配
1 权限控制
权限控制机制的本质就是 “用钥匙开锁”。
2 给Admin分配Role
2.1 目标
2.2 思路
2.3 代码:前往分配页面
2.3.1 创建保存 Admin-Role 关联关系的数据库表
2.3.2 修改 “分配” 按钮
旧代码:
<button type="button" class="btn btn-success btn-xs"> <i class="glyphicon glyphicon-check"></i> </button>
|
新代码:
<a href="assign/to/assign/role/page.html?adminId=${admin.id }&pageNum=${requestScope.pageInfo.pageNum }&keyword=${param.keyword }" class="btn btn-success btn-xs"> <i class="glyphicon glyphicon-check"></i> </a>
|
2.3.3 创建AssignHandler
package org.example.mvc.handler;
import org.example.entity.Role; import org.example.service.api.AdminService; import org.example.service.api.RoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/** * @author 123 * date 2023-02-05 */ @Controller public class AssignHandler {
@Autowired private AdminService adminService;
@Autowired private RoleService roleService;
@RequestMapping("/assign/to/assign/role/page.html") public String toAssignRolePage( @RequestParam("adminId") Integer adminId, ModelMap modelMap ) { // 1.查询已分配角色 List<Role> assignedRoleList = roleService.getAssignedRole(adminId); // 2.查询未分配角色 List<Role> unAssignedRoleList = roleService.getUnAssignedRole(adminId); // 3.存入模型(本质上其实是:request.setAttribute("attrName",attrValue); modelMap.addAttribute("assignedRoleList", assignedRoleList); modelMap.addAttribute("unAssignedRoleList", unAssignedRoleList); return "assign-role"; }
}
|
2.3.4 RoleService中的方法
RoleService:
List<Role> getAssignedRole(Integer adminId);
List<Role> getUnAssignedRole(Integer adminId);
|
RoleServiceImpl:
@Override public List<Role> getAssignedRole(Integer adminId) { return roleMapper.selectAssignedRole(adminId); }
@Override public List<Role> getUnAssignedRole(Integer adminId) { return roleMapper.selectUnAssignedRole(adminId); }
|
2.3.5 SQL语句
RoleMapper.java:
List<Role> selectAssignedRole(Integer adminId);
List<Role> selectUnAssignedRole(Integer adminId);
|
RoleMapper.xml:
<select id="selectAssignedRole" resultMap="BaseResultMap"> select id,name from t_role where id in (select role_id from inner_admin_role where admin_id=#{adminId}) </select> <select id="selectUnAssignedRole" resultMap="BaseResultMap"> select id,name from t_role where id not in (select role_id from inner_admin_role where admin_id=#{adminId}) </select>
|
2.3.6 在页面上显示角色数据
页面具体代码:
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <ol class="breadcrumb"> <li><a href="#">首页</a></li> <li><a href="#">数据列表</a></li> <li class="active">分配角色</li> </ol> <div class="panel panel-default"> <div class="panel-body"> <form type="post" role="form" class="form-inline"> <div class="form-group"> <%--@declare id="exampleinputpassword1"--%><label for="exampleInputPassword1">未分配角色列表</label><br> <select class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.unAssignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <div class="form-group"> <ul> <li class="btn btn-default glyphicon glyphicon-chevron-right"></li> <br> <li class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li> </ul> </div> <div class="form-group" style="margin-left:40px;"> <label for="exampleInputPassword1">已分配角色列表</label><br> <select name="roleId" class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.assignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> </form> </div> </div> </div> </div> </div> </body> </html>
|
2.3.7 调整表单让表单能够提交数据
参考下面文件:
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %>
<body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <ol class="breadcrumb"> <li><a href="#">首页</a></li> <li><a href="#">数据列表</a></li> <li class="active">分配角色</li> </ol> <div class="panel panel-default"> <div class="panel-body"> <form action="assign/do/role/assign.html" type="post" role="form" class="form-inline"> <input type="hidden" name="adminId" value="${param.adminId }"> <input type="hidden" name="pageNum" value="${param.pageNum }"> <input type="hidden" name="keyword" value="${param.keyword }">
<div class="form-group"> <%--@declare id="exampleinputpassword1"--%><label for="exampleInputPassword1">未分配角色列表</label><br> <select class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.unAssignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <div class="form-group"> <ul> <li id="toRightBtn" class="btn btn-default glyphicon glyphicon-chevron-right"></li> <br> <li id="toLeftBtn" class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li> </ul> </div> <div class="form-group" style="margin-left:40px;"> <label for="exampleInputPassword1">已分配角色列表</label><br> <select name="roleIdList" class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.assignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <button type="submit" style="width: 150px;" class="btn btn-lg btn-success btn-block">保存</button> </form> </div> </div> </div> </div> </div> </body> </html>
|
2.3.8 jQuery代码
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %> <script type="text/javascript">
$(function () { $("#toRightBtn").click(function(){ // select 是标签选择器 // :eq(0)表示选择页面上的第一个 // :eq(1)表示选择页面上的第二个 // “>”表示选择子元素 // :selected 表示选择“被选中的”option // appendTo()能够将 jQuery 对象追加到指定的位置 $("select:eq(0)>option:selected").appendTo("select:eq(1)"); }) $("#toLeftBtn").click(function(){ // select 是标签选择器 // :eq(0)表示选择页面上的第一个 // :eq(1)表示选择页面上的第二个 // “>”表示选择子元素 // :selected 表示选择“被选中的”option // appendTo()能够将 jQuery 对象追加到指定的位置 $("select:eq(1)>option:selected").appendTo("select:eq(0)"); }) });
</script> <body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <ol class="breadcrumb"> <li><a href="#">首页</a></li> <li><a href="#">数据列表</a></li> <li class="active">分配角色</li> </ol> <div class="panel panel-default"> <div class="panel-body"> <form action="assign/do/role/assign.html" type="post" role="form" class="form-inline"> <input type="hidden" name="adminId" value="${param.adminId }"> <input type="hidden" name="pageNum" value="${param.pageNum }"> <input type="hidden" name="keyword" value="${param.keyword }">
<div class="form-group"> <%--@declare id="exampleinputpassword1"--%><label for="exampleInputPassword1">未分配角色列表</label><br> <select class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.unAssignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <div class="form-group"> <ul> <li id="toRightBtn" class="btn btn-default glyphicon glyphicon-chevron-right"></li> <br> <li id="toLeftBtn" class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li> </ul> </div> <div class="form-group" style="margin-left:40px;"> <label for="exampleInputPassword1">已分配角色列表</label><br> <select name="roleIdList" class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.assignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <button type="submit" style="width: 150px;" class="btn btn-lg btn-success btn-block">保存</button> </form> </div> </div> </div> </div> </div> </body> </html>
|
2.4 代码:执行分配
2.4.1 handler方法
AssignHandler.java:
@RequestMapping("/assign/do/role/assign.html") public String saveAdminRoleRelationship( @RequestParam("adminId") Integer adminId, @RequestParam("pageNum") Integer pageNum, @RequestParam("keyword") String keyword, // 我们允许用户在页面上取消所有已分配角色再提交表单,所以可以不提供roleIdList 请求参数 // 设置 required=false 表示这个请求参数不是必须的 @RequestParam(value = "roleIdList", required = false) List<Integer> roleIdList ) { adminService.saveAdminRoleRelationship(adminId, roleIdList); return "redirect:/admin/get/page.html?pageNum=" + pageNum + "&keyword=" + keyword; }
|
2.4.2 service方法
AdminService:
void saveAdminRoleRelationship(Integer adminId, List<Integer> roleIdList);
|
AdminServiceImpl:
@Override public void saveAdminRoleRelationship(Integer adminId, List<Integer> roleIdList) { // 为了简化操作:先根据 adminId 删除旧的数据,再根据 roleIdList 保存全部新的数据 // 1.根据 adminId 删除旧的关联关系数据 adminMapper.deleteOLdRelationship(adminId); // 2.根据 roleIdList 和 adminId 保存新的关联关系 if (roleIdList != null && roleIdList.size() > 0) { adminMapper.insertNewRelationship(adminId, roleIdList); } }
|
2.4.3 SQL语句
adminmapper.java:
void deleteOLdRelationship(Integer adminId);
void insertNewRelationship(
@Param("adminId") Integer adminId,@Param("roleIdList") List<Integer> roleIdList
);
|
adminmapper.xml:
<delete id="deleteOLdRelationship"> delete from inner_admin_role where admin_id=#{adminId} </delete>
|
<insert id="insertNewRelationship"> insert into inner_admin_role(admin_id,role_id) values <foreach collection="roleIdList" item="roleId" separator=",">(#{adminId},#{roleId})</foreach> </insert>
|
报错:
idea控制台异常:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'roleIdList' not found. Available parameters are [0, 1, param1, param2] at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:75) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:371) at com.sun.proxy.$Proxy14.insert(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:240) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:51) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52) at com.sun.proxy.$Proxy15.insertNewRelationship(Unknown Source) at org.example.service.impl.AdminServiceImpl.saveAdminRoleRelationship(AdminServiceImpl.java:145) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy18.saveAdminRoleRelationship(Unknown Source) at org.example.mvc.handler.AssignHandler.saveAdminRoleRelationship(AssignHandler.java:37) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:849) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:760) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
|
解决:
出现该报错的大部分原因是mapper.xml文件中的参数没有对应匹配。
当传入参数只有一个时候,就不需要设定@Param。接口参数只有一个,不管接口参数名是什么,这个时候#{xxx}没有限制,可以是0,param1,也可以是aaa,bbb。可以不加注解。
当传入的的参数>1的时候,就会出现该报错:mybatis的参数集就是上边说的默认值[0, 1, param1, param2] ,如果你不用默认值,就需要加上@Param 注解起别名。一旦加了注解,mybatis的参数集就和第一种情况一样了。
|
2.4.4 修正Bug
assign-role.jsp:
<%-- Created by IntelliJ IDEA. User: 123 Date: 2023/1/15 Time: 14:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html lang="zh-CN"> <%@include file="/WEB-INF/include-head.jsp" %> <script type="text/javascript">
$(function () { $("#toRightBtn").click(function(){ // select 是标签选择器 // :eq(0)表示选择页面上的第一个 // :eq(1)表示选择页面上的第二个 // “>”表示选择子元素 // :selected 表示选择“被选中的”option // appendTo()能够将 jQuery 对象追加到指定的位置 $("select:eq(0)>option:selected").appendTo("select:eq(1)"); }) $("#toLeftBtn").click(function(){ // select 是标签选择器 // :eq(0)表示选择页面上的第一个 // :eq(1)表示选择页面上的第二个 // “>”表示选择子元素 // :selected 表示选择“被选中的”option // appendTo()能够将 jQuery 对象追加到指定的位置 $("select:eq(1)>option:selected").appendTo("select:eq(0)"); }) }); $("#submitBtn").click(function(){ // 在提交表单前把“已分配”部分的 option 全部选中 $("select:eq(1)>option").prop("selected","selected"); });
</script> <body> <%@include file="/WEB-INF/include-nav.jsp" %> <div class="container-fluid"> <div class="row"> <%@include file="/WEB-INF/include-sidebar.jsp" %> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <ol class="breadcrumb"> <li><a href="#">首页</a></li> <li><a href="#">数据列表</a></li> <li class="active">分配角色</li> </ol> <div class="panel panel-default"> <div class="panel-body"> <form action="assign/do/role/assign.html" type="post" role="form" class="form-inline"> <input type="hidden" name="adminId" value="${param.adminId }"> <input type="hidden" name="pageNum" value="${param.pageNum }"> <input type="hidden" name="keyword" value="${param.keyword }">
<div class="form-group"> <%--@declare id="exampleinputpassword1"--%><label for="exampleInputPassword1">未分配角色列表</label><br> <select class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.unAssignedRoleList }" var="role"> <option value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <div class="form-group"> <ul> <li id="toRightBtn" class="btn btn-default glyphicon glyphicon-chevron-right"></li> <br> <li id="toLeftBtn" class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li> </ul> </div> <div class="form-group" style="margin-left:40px;"> <label for="exampleInputPassword1">已分配角色列表</label><br> <select name="roleIdList" class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;"> <c:forEach items="${requestScope.assignedRoleList }" var="role"> <option selected="selected" value="${role.id }">${role.name }</option> </c:forEach> </select> </div> <button id="submitBtn" type="submit" style="width: 150px;" class="btn btn-lg btn-success btn-block">保存</button> </form> </div> </div> </div> </div> </div> </body> </html>
|
3 给Role分配Auth
3.1 目标
3.2 思路
3.3 代码:数据库(Auth建表)
INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (1, '', '用户模块', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (2, 'user:delete', '删除', 1); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (3, 'user:get', '查询', 1); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (4, '', '角色模块', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (5, 'role:delete', '删除', 4); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (6, 'role:get', '查询', 4); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (7, 'role:add', '新增', 4); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (8, 'user:save', '保存', 1); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (9, 'test', 'test', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (10, 'test', 'test', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (11, 'test', 'test', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (12, 'testTx', 'testTx', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (13, 'testTx', 'testTx', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (14, 'testTx', 'testTx', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (15, 'testTx', 'testTx', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (16, 'test', 'test', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (17, 'testTx', 'testTx', NULL); INSERT INTO `fundingonline_study`.`t_auth`(`id`, `name`, `title`, `category_id`) VALUES (18, 'test', 'test', NULL);
|
3.4 代码:打开模态框
3.4.1 准备模态框
<%@include file="/WEB-INF/modal-role-assign-auth.jsp" %>
|
3.4.2 给 “√” 按钮绑定单击响应函数
在FillTableBody()函数中,修改checkBtn:
var checkBtn = "<button id='" + roleId + "' type='button' class='btn btn-success btn-xs checkBtn'><i class=' glyphicon glyphicon-check'></i></button>";
|
// 13.给分配权限按钮绑定单击响应函数 $("#rolePageBody").on("click", ".checkBtn", function () { // 把当前角色id存入全局变量 window.roleId = this.id; // 打开模态框 $("#assignModal").modal("show"); // 在模态框中装载树 Auth 的形结构数据 fillAuthTree(); });
|
3.4.3 加入zTree的环境
<link rel="stylesheet" href="ztree/zTreeStyle.css"/> <script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script>
|
注:引入时,zTree的JavaScript库文件要放在 my-role.js 文件之前。
3.4.4 显示不正常分析
3.4.5 函数 fillAuthTree()
// 声明专门的函数用来分配Auth的模态框中显示Auth的树形结构数据 function fillAuthTree() { // 1.发送 Ajax 请求查询 Auth 数据 var ajaxReturn = $.ajax({ "url":"assgin/get/all/auth.json", "type":"post", "dataType":"json", "async":false }); if(ajaxReturn.status != 200) { layer.msg("请求处理出错响应状态码 是 : "+ajaxReturn.status+" 说 明 是 :"+ajaxReturn.statusText); return ; } // 2.从响应结果中获取 Auth 的 JSON 数据 // 从服务器端查询到的 list 不需要组装成树形结构,这里我们交给 zTree 去组装 var authList = ajaxReturn.responseJSON.data; // 3.准备对 zTree 进行设置的 JSON 对象 var setting = { "data": { "simpleData": { // 开启简单 JSON 功能 "enable": true, // 使用 categoryId 属性关联父节点,不用默认的 pId 了 "pIdKey": "categoryId" }, "key": { // 使用 title 属性显示节点名称,不用默认的 name 作为属性名了 "name": "title" } }, "check": { "enable": true } }; // 4.生成树形结构 // <ul id="authTreeDemo" class="ztree"></ul> $.fn.zTree.init($("#authTreeDemo"), setting, authList); // 获取 zTreeObj 对象 var zTreeObj = $.fn.zTree.getZTreeObj("authTreeDemo"); // 调用 zTreeObj 对象的方法,把节点展开 zTreeObj.expandAll(true);
// 5.查询已分配的 Auth 的 id 组成的数组 ajaxReturn = $.ajax({ "url":"assign/get/assigned/auth/id/by/role/id.json", "type":"post", "data":{ "roleId":window.roleId }, "dataType":"json", "async":false }); if(ajaxReturn.status != 200) { layer.msg("请求处理出错!响应状态码是:"+ajaxReturn.status+"说明是:"+ajaxReturn.statusText); return ; } // 从响应结果中获取 authIdArray var authIdArray = ajaxReturn.responseJSON.data; // 6.根据 authIdArray 把树形结构中对应的节点勾选上 // ①遍历 authIdArray for(var i = 0; i < authIdArray.length; i++) { var authId = authIdArray[i]; // ②根据 id 查询树形结构中对应的节点 var treeNode = zTreeObj.getNodeByParam("id", authId); // ③将 treeNode 设置为被勾选 // checked 设置为 true 表示节点勾选 var checked = true; // checkTypeFlag 设置为 false,表示不“联动”,不联动是为了避免把不该勾选的勾选上 var checkTypeFlag = false; // 执行 zTreeObj.checkNode(treeNode, checked, checkTypeFlag); } }
|
3.4.6 创建角色到权限之间关联关系的中间表
INSERT INTO `fundingonline_study`.`inner_role_auth`(`id`, `role_id`, `auth_id`) VALUES (1, 1, 2); INSERT INTO `fundingonline_study`.`inner_role_auth`(`id`, `role_id`, `auth_id`) VALUES (8, 238, 1); INSERT INTO `fundingonline_study`.`inner_role_auth`(`id`, `role_id`, `auth_id`) VALUES (9, 238, 8); INSERT INTO `fundingonline_study`.`inner_role_auth`(`id`, `role_id`, `auth_id`) VALUES (12, 239, 4); INSERT INTO `fundingonline_study`.`inner_role_auth`(`id`, `role_id`, `auth_id`) VALUES (13, 239, 5);
|
3.4.7 根据 role_id 查询 auth_id
// Assignhandler方法
@RequestMapping("/assign/get/assigned/auth/id/by/role/id.json") public ResultEntity<List<Integer>> getAssignedAuthIdByRoleId( @RequestParam("roleId") Integer roleId) { List<Integer> authIdList = authService.getAssignedAuthIdByRoleId(roleId); return ResultEntity.successWithData(authIdList); }
|
// AuthService.java
List<Integer> getAssignedAuthIdByRoleId(Integer roleId);
|
// AuthServiceImpl.java
@Override public List<Integer> getAssignedAuthIdByRoleId(Integer roleId) { return authMapper.selectAssignedAuthIdByRoleId(roleId); }
|
// AuthMapper.java
List<Integer> selectAssignedAuthIdByRoleId(Integer roleId);
|
// AuthMapper.xml
<select id="selectAssignedAuthIdByRoleId" resultType="int"> select auth_id from inner_role_auth where role_id=#{roleId} </select>
|
3.5 代码:执行分配
3.5.1 给 “分配” 按钮绑定单击响应函数
// 14.给分配权限模态框中的“分配”按钮绑定单击响应函数 $("#assignBtn").click(function () { // ①收集树形结构的各个节点中被勾选的节点 // [1]声明一个专门的数组存放 id var authIdArray = []; // [2]获取 zTreeObj 对象 var zTreeObj = $.fn.zTree.getZTreeObj("authTreeDemo"); // [3]获取全部被勾选的节点 var checkedNodes = zTreeObj.getCheckedNodes(); // [4]遍历 checkedNodes for (var i = 0; i < checkedNodes.length; i++) { var checkedNode = checkedNodes[i]; var authId = checkedNode.id; authIdArray.push(authId); } // ②发送请求执行分配 var requestBody = { "authIdArray":authIdArray, // 为了服务器端 handler 方法能够统一使用 List<Integer>方式接收数据,roleId 也存入数组 "roleId":[window.roleId] } requestBody = JSON.stringify(requestBody); $.ajax({ "url": "assign/do/role/assign/auth.json", "type": "post", "data": requestBody, "contentType": "application/json;charset=UTF-8", "dataType": "json", "success": function (response) { var result = response.result; if (result == "SUCCESS") { layer.msg("操作成功!"); } if (result == "FAILED") { layer.msg("操作失败!" + response.message); } }, "error": function (response) { layer.msg(response.status + " " + response.statusText); } }); $("#assignModal").modal("hide"); });
|
3.5.2 后端执行分配,保存关联关系
// AssignHanlder.java
@ResponseBody @RequestMapping("/assign/do/role/assign/auth.json") public ResultEntity<String> saveRoleAuthRelathinship( @RequestBody Map<String, List<Integer>> map) { authService.saveRoleAuthRelathinship(map); return ResultEntity.successWithoutData(); }
|
// RoleService.java
void saveRoleAuthRelathinship(Map<String, List<Integer>> map);
|
// RoleServiceImpl.java
@Override public void saveRoleAuthRelathinship(Map<String, List<Integer>> map) { // 1.获取 roleId 的值 List<Integer> roleIdList = map.get("roleId"); Integer roleId = roleIdList.get(0); // 2.删除旧关联关系数据 authMapper.deleteOldRelationship(roleId); // 3.获取 authIdList List<Integer> authIdList = map.get("authIdArray"); // 4.判断 authIdList 是否有效 if (authIdList != null && authIdList.size() > 0) { authMapper.insertNewRelationship(roleId, authIdList); } }
|
// RoleMapper.java
void deleteOldRelationship(Integer roleId);
void insertNewRelationship(@Param("roleId") Integer roleId, @Param("authIdList") List<Integer> authIdList);
|
// RoleMapper.xml
<delete id="deleteOldRelationship"> delete from inner_role_auth where role_id=#{roleId} </delete> <insert id="insertNewRelationship"> insert into inner_role_auth(auth_id,role_id) values <foreach collection="authIdList" item="authId" separator=",">(#{authId},#{roleId}) </foreach> </insert>
|
4 给Menu分配Auth
略
报错
0、将两个项目放一个界面展示
1、若将*Mapper.xml放在resources/mybatis/mapper下,sqlSessionFactoryBean里配置mapperLocation的value=“mybatis/mapper/*Mapper.xml”,就报错无法识别该路径
若如下,就能识别*Mapper.xml:
解决方法:https://blog.csdn.net/m0_72934541/article/details/126699156
重新建包,一级一级建,成果:
2、
解决方法:加载配置文件将tx的路径加上(
// 加载Spring配置文件的注解
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml", "classpath:spring-persist-tx.xml"})
)
3、
到这里卡主不动,数据未提交至数据库。
解决方法:可能是AdminService或AdminServiceImpl文件编写错误。
4、spring-persist-tx.xml中的ref="dataSource"爆红
解决办法:
- 产生的原因是spring的配置文件没有导入,需要手动导入
- 点击File——》Project structure——》
- 点击你需要添加的模块的配置文件(spring)——》点击+——》勾选上配置文件——》点击OK——》点击apply
5、测试SSM整合:运行index.jsp页面报404
解决方法:URL改为http://localhost:7080//atcrowdfunding002_admin_webui_war_exploded/
因为:
解决方法二:将上图修改为“/”
备份:
INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (1, NULL, '系统权限菜单', NULL, 'glyphicon\r\nglyphicon-th-list');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (2, 1, '控 制 面 板', 'main.htm', 'glyphicon glyphicon glyphicon-tasks');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (3, 1, '权限管理', NULL, 'glyphicon glyphicon\r\nglyphicon-tasks');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (4, 3, ' 用 户 维 护 ', 'user/index.htm', 'glyphicon\r\nglyphicon-user');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (5, 3, ' 角 色 维 护 ', 'role/index.htm', 'glyphicon\r\nglyphicon-king');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (6, 3, ' 菜 单 维 护 ', 'permission/index.htm', 'glyphicon\r\nglyphicon-lock');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (7, 1, ' 业 务 审 核 ', NULL, 'glyphicon\r\nglyphicon-ok');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (8, 7, '实名认证审核', 'auth_cert/index.htm', 'glyphicon\r\nglyphicon-check');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (9, 7, ' 广 告 审 核 ', 'auth_adv/index.htm', 'glyphicon\r\nglyphicon-check');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (10, 7, ' 项 目 审 核 ', 'auth_project/index.htm', 'glyphicon\r\nglyphicon-check');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (11, 1, ' 业 务 管 理 ', NULL, 'glyphicon\r\nglyphicon-th-large');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (12, 11, ' 资 质 维 护 ', 'cert/index.htm', 'glyphicon\r\nglyphicon-picture');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (13, 11, ' 分 类 管 理 ', 'certtype/index.htm', 'glyphicon\r\nglyphicon-equalizer');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (14, 11, ' 流 程 管 理 ', 'process/index.htm', 'glyphicon\r\nglyphicon-random');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (15, 11, ' 广 告 管 理 ', 'advert/index.htm', 'glyphicon\r\nglyphicon-hdd');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (16, 11, ' 消 息 模 板 ', 'message/index.htm', 'glyphicon\r\nglyphicon-comment');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (17, 11, ' 项 目 分 类 ', 'projectType/index.htm', 'glyphicon\r\nglyphicon-list');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (18, 11, ' 项 目 标 签 ', 'tag/index.htm', 'glyphicon\r\nglyphicon-tags');INSERT INTO `fundingonline_study`.`t_menu`(`id`, `pid`, `name`, `url`, `icon`) VALUES (19, 1, ' 参 数 管 理 ', 'param/index.htm', 'glyphicon\r\nglyphicon-list-alt');
SpringSecurity
1 SpringSecrity框架用法介绍
2 权限管理过程中的相关概念
2.1 主体
2.2 认证
2.3 授权
3 权限管理的主流框架
3.1 SpringSecurity
Spring技术栈的组成部分。
3.2 Shiro
4 使用配置类代替XML
4.1 @Configuration 注解
类标记了这个注解就可以使用这个代替spring的XML配置文件。
4.2 @Bean注解
用来代替XML配置文件中的bean标签,下面两种形式效果一致:
5 HelloWorld 工程创建步骤
5.1 创建Maven的Web工程
5.2 加入SpringMVC环境需要的依赖
<!--这里并不是使用springMVC,仅仅是借助这个依赖把Spring的环境导入--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.20.RELEASE</version> </dependency> <!-- 引入 Servlet 容器中相关依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- JSP 页面使用的依赖 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1.3-b06</version> <scope>provided</scope> </dependency>
|
5.3 创建SpringMVC 配置文件
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 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-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
<context:component-scan base-package="spring"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INS/views/"></property> <property name="suffix" value=".jsp"></property> </bean>
<mvc:annotation-driven></mvc:annotation-driven> <mvc:default-servlet-handler/>
</beans>
|
5.4 在web.xml中配置DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet> <servlet-name>springDispatcherServlet</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> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
</web-app>
|
5.5 创建包
security.controller
5.6 从例子工程中复制Controller
package security.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping;
/** * @author 123 * date 2023-02-07 */ @Controller public class AdminController {
@GetMapping("main.html") public String main() { return "main"; }
@RequestMapping("to/no/auth/page.html") public String toNoAuthPage() { return "no_auth"; }
}
|
5.7 加入webapp目录下文件
6 在HelloWorld基础上加入SpringSecurity
6.1 加入SpringSecurity依赖
<!-- 声明属性, 对 SpringSecurity 的版本进行统一管理 --> <atguigu.spring.security.version>4.2.10.RELEASE</atguigu.spring.security.version>
<!-- SpringSecurity 标签库 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.10.RELEASE</version> </dependency>
|
6.2 加入SpringSecurity控制权限的Filter
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!--这个Filter要在所有Filter前面--> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
6.3 加入配置类
6.4 效果
7 SpringSecurity操作实验
7.1 实验1:放行首页和静态资源
重写后:
进一步修改:
↓
.
.
.
实验9
09-后台管理系统-分配
1 SpringSecurity回顾
1.1 准备工作
准备SpringMVC的环境,发送请求访问资源时完全没有权限。
1.2 加入SpringSecurity环境
1.2.1 加入SpringSecurity依赖
<!-- 声明属性, 对 SpringSecurity 的版本进行统一管理 --> <atguigu.spring.security.version>4.2.10.RELEASE</atguigu.spring.security.version>
<!-- SpringSecurity 标签库 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.10.RELEASE</version> </dependency>
|
1.2.2 在web.xml中配置Filter
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!--这个Filter要在所有Filter前面--> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
1.2.3 创建基于注解的配置类
package com.atguigu.crowd.mvc.config;
import com.ccctop.crowd.constant.CrowdConstant; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public BCryptPasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder builder) throws Exception { // 与SpringSecurity环境下用户登录相关
/*builder.inMemoryAuthentication().withUser("tom").password("123123").roles("ADMIN");*/ builder.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder()); }
@Override protected void configure(HttpSecurity security) throws Exception { // 与SpringSecurity环境下请求授权相关
security.authorizeRequests() .antMatchers("/admin/to/login/page.html", "/bootstrap/**", "/crowd/**", "/css/**", "/fonts/**", "/img/**", "/jquery/**", " /layer/**", "/script/**", "/ztree/**") .permitAll() .antMatchers("/admin/get/page.html") .hasRole("经理") .anyRequest() .authenticated() .and() .exceptionHandling() //.accessDeniedPage("/to/no/auth/page.html") .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletRequest.setAttribute("message", CrowdConstant.MESSAGE_LOGIN_DENIED); httpServletRequest.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(httpServletRequest, httpServletResponse); } }) .and() .csrf() .disable() .formLogin() .loginPage("/admin/to/login/page.html") .permitAll() .loginProcessingUrl("/security/do/login.html") .permitAll() .usernameParameter("loginAcct") .passwordParameter("userPswd") .defaultSuccessUrl("/admin/to/main/page.html") .and() .logout() .logoutUrl("/security/do/logout.html") .logoutSuccessUrl("/admin/to/login/page.html") ; } }
|
1.3 放行首页和静态资源
1.4 未认证请求跳转到登录页面
1.5 实现完整的登录流程
1.5.1 设置表单
1.5.2 设置正确的账号、密码
1.6 退出登录
1.7 基于角色或权限实现访问控制
1.8 指定403页面
1.9 记住我-内存版
1.10 记住我-数据库版(不重要)
1.10.1 加入依赖
1.10.2 创建数据库
1.10.3 配置数据源
1.10.4 在配置类中装配数据源
1.10.5 创建数据库表
1.11 查数据库完成认证
1.12 密码加密
1.12.1 认识SpringSecurity提供的加密接口
1.12.2 创建PasswordEncoder实现类
1.12.3 在SpringSecurity的配置类中装配MyPasswordEncoder
1.12.4 使用MyPasswordEncoder对象
1.12.5 潜在的问题
1.13 带盐值的加密
2 项目中加入SpringSecurity
2.1 加入SpringSecurity环境(-229)
2.1.1 依赖
parent pom.xml:
<!-- 声明属性, 对 SpringSecurity 的版本进行统一管理 --> <spring.security.version>4.2.10.RELEASE</spring.security.version>
<!-- SpringSecurity 标签库 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.webjars.bower</groupId> <artifactId>jquery</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.10.RELEASE</version> </dependency>
|
component pom.xml:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> </dependency>
|
2.1.2 在web.xml中配置DelegatingFilterProxy
webui web.xml:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!--这个Filter要在所有Filter前面--> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
2.1.3 创建基于注解的配置类
// 表示当前类是一个配置类 @Configuration
// 启用Web环境下权限控制功能 @EnableWebSecurity public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
}
|
2.1.4 谁来把 WebAppSecurityConfig配置类 扫描到Ioc容器中
2.2 提出找不到bean的问题
2.3 分析上面那个问题
2.3.1 明确三大组件启动顺序
2.3.2 Filter查找IOC容器然后查找bean的工作机制
2.4 解决方案一:把两个IOC容器合二为一
2.5 解决方案二:改源码
2.6 意外收获
2.7 目标1:放行登录页和静态资源
2.7.1 思路
2.7.2 代码
package org.example.mvc.config;
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
// 表示当前类是一个配置类 @Configuration
// 启用Web环境下权限控制功能 @EnableWebSecurity public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity security) throws Exception { security.authorizeRequests() .antMatchers("/admin/to/login/page.html") .permitAll() .antMatchers("/bootstrap/**") .permitAll() .antMatchers("/crowd/**") .permitAll() .antMatchers("/css/**") .permitAll() .antMatchers("/fonts/**") .permitAll() .antMatchers("/img/**") .permitAll() .antMatchers("/jquery/**") .permitAll() .antMatchers("/layer/**") .permitAll() .antMatchers("/script/**") .permitAll() .antMatchers("/WEB-INF/**") .permitAll() .antMatchers("/ztree/**") .permitAll() .anyRequest() .authenticated(); } }
|
测试:进入登录“/admin/to/login/page.html” 报404 ?
此问题未解决,导致SpringSecurity版块未继续进行,未完成(229-258 未做)。
2.8 目标2:提交登录表单做内存认证
2.8.1 思路
2.8.2 操作1:设置表单
271-328未做
-----------------------------------------------------------------------------------------------------------------------------------
2 尚筹网会员系统总目标
环境-SpringBoot+springcloud
首页-众筹项目的展示
注册会员
会员登录
个人中心
发起众筹
参与别人的项目
后台进行业务审核
3 会员系统架构
3.1 架构图
3.2 需要创建的工程
atcrowdfunding07-member-parent
atcrowdfunding08-member-eureka
atcrowdfunding09-member-entity
atcrowdfunding10-member-mysql-provider
atcrowdfunding11-member-redis-provider
atcrowdfunding12-member-authentication-consumer
atcrowdfunding13-member-project-consumer
atcrowdfunding14-member-order-consumer
atcrowdfunding15-member-pay-consumer
atcrowdfunding16-member-zuul
atcrowdfunding17-member-api
4 parent工程配置pom.xml
<!--配置在父工程中要管理的依赖--> <dependencyManagement> <dependencies> <!--导入SpringCloud需要用的依赖信息 ${spring-cloud.version}--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <!--import依赖范围表示将spring-cloud-dependencies包中的依赖信息导入--> <scope>import</scope> </dependency> <!--导入SpringBoot需要用的依赖信息--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> </dependencies> </dependencyManagement>
|
5 搭建环境约定
5.1 包名约定
新创建的包都作为org.example的子包
5.2 主启动类类名
CrowdMainClass
5.3 端口号
6 eureka工程
6.1 依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
|
报错:
依赖爆红,提示没有版本,加入版本,提示没有该版本。
解决:
https://blog.csdn.net/qq_35760825/article/details/125153435
6.2 主启动类 CrowdMainClass
package org.example;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/** * @author 123 * date 2023-02-21 */ @EnableEurekaServer @SpringBootApplication public class CrowdMainClass {
public static void main(String[] args) { SpringApplication.run(CrowdMainClass.class, args); }
}
|
6.3 application.yml
server: port: 1000 spring: application: name: atguigu-crowd-eureka freemarker: prefer-file-system-access: false eureka: instance: hostname: localhost client: registerWithEureka: false # 自己就是注册中心,所以自己不注册自己 fetchRegistry: false # 自己就是注册中心,所以不需要“从注册中心取回信息” serviceUrl: # 客户端访问 Eureka 时使用的地址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
测试:
7 entity工程
7.1 实体类的进一步细分
7.2 创建包
org.example.entity.po
org.example.entity.vo
7.3 lombok
7.3.1 效果
7.3.2 lombok原理
7.3.3 Eclipse设置
7.3.3.1-2 替换为安装插件(因为是用的idea)
(逆向生成t_member数据库表,
给MemberPOMapper类添加@Service注解。)
generatorConfig.xml:
<javaModelGenerator targetProject=".\src\main\java" targetPackage="org.example.entity.po">
<table tableName="t_member" domainObjectName="MemberPO" />
|
MemberPO类:
@AllArgsConstructor @NoArgsConstructor @Data
|
测试报错:
解决:
idea中包的创建若连续创建(如:mapp.ex),则路径显示为“mapp.ex”,而非“mapp/ex”。
方法一:修改yml
方法二(我采用):
不修改yml文件,重新建mybatis/mapper,注意分两次建立,不可“bybatis.mapper”这样建立。
接下来 目录按照尚硅谷官方笔记进行补充(官方笔记保存在百度网盘中)
9.1.1(342-) 导入 spring-cloud-starter-openfeign 依赖失败
解决:
将阿里源切换回本地库,再加个与SpringBoot匹配的SpringCloud版本号
将这个方法的url切换成本地库的就为此问题解决方法:https://blog.csdn.net/qq_35760825/article/details/125153435
redis
345:学习redis
https://www.cnblogs.com/ancestor/p/10816163.html
虚拟机密码:111
redis常用命令↓
Linux下载好redis后:
root下,启动redis服务:redis-server /myredis/redis.conf
(关闭服务器,再次打开服务器)
进入root路径: su root
密码:111
进入redis: redis-cli
测试命令行:ping
测试成功会得到:PONG
退出redies客户端:quit
对redis常规操作一套流程:
启动redis:redis-server /myredis/redis.conf
(关闭服务器,再次打开服务器)
查看redis是否还在运行:ps aux | grep redis
进入redis: redis-cli
测试命令行:ping
测试成功会得到:PONG
输入密码进行操作:auth 1
查看所有key-value:key *
设置key-value:(config)set key值 value值
查看key对应的value:(config)get key值
退出redies客户端:quit
关闭redis:redis-cli shutdown
删除redis:
rm -f /usr/local/redis/bin/redis*
rm -rf redis
bind必须配成“192.168.201.100”,因为:(此处说法应该是错的,只要是外网ip就可,并非必须192.168.201.100)
打错了,应该是:192.168.201.100
配置静态ip-只为方便(不然每次启动虚拟机都会变) 非必须(似乎失败):
https://blog.csdn.net/dengxw00/article/details/128551924
TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="static"
DEFROUTE="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
NAME="ens33"
UUID="92d6ac42-7e8e-455d-9d5e-db5046c27dc9"
DEVICE="ens33"
ONBOOT="yes"
IPADDR="192.168.8.101"
DNS1="192.168.8.2"
GATEWAY="192.168.8.2"
|
在windows系统中打开cmd 输入:telnet 192.168.56.1 6379 (telnet ip port)测试是否可以正常连接,如果能联通则表示可以正常连接。
SpringBoot整合redis:
第一步:redis跑起来
第二步:idea工程环境配置
第三步:测试
测试报错:
org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 192.168.201.100:6379
at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceConnectionFactory.java:1106) at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getConnection(LettuceConnectionFactory.java:1085) at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.getSharedConnection(LettuceConnectionFactory.java:866) at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.getConnection(LettuceConnectionFactory.java:341) at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:132) at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:95) at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:82) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:211) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184) at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95) at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:236) at org.example.test.RedisTest.testSet(RedisTest.java:34) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53) Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to 192.168.201.100:6379 at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:78) at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:56) at io.lettuce.core.AbstractRedisClient.getConnection(AbstractRedisClient.java:235) at io.lettuce.core.RedisClient.connect(RedisClient.java:204) at org.springframework.data.redis.connection.lettuce.StandaloneConnectionProvider.lambda$getConnection$1(StandaloneConnectionProvider.java:113) at java.util.Optional.orElseGet(Optional.java:267) at org.springframework.data.redis.connection.lettuce.StandaloneConnectionProvider.getConnection(StandaloneConnectionProvider.java:113) at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceConnectionFactory.java:1104) ... 41 more
|
解决:
idea的yml配置文件中,
redis host赋值为:192.168.8.130
密码赋值为:1
问题根源:静态ip未设置成功,连接是依旧用的虚拟机自动生成的ip,如此的弊端,有可能每次启动虚拟机ip都会变动。
问题:为什么运行那些CrowdMainClass.java文件,控制台会打印下图异常,但是依旧能运行前端代码。
解决:
euraka工程的yml文件service-url下的defaultZone没有缩进:
13.3 (351):用localhost访问portal.html页面页面报错
理应如此:
解决:
可能是清理缓存
注册获取验证码,
问题1
控制台报错: Illegal character in scheme name at index 4: http(s)://gyytz.market.alicloud......................
解决:yml中url路径成分写错了。改为https://gyytz....com
问题2
阿里云登录https://account.aliyun.com/login/login.htm?spm=a2c6h.12873639.J_3207526240.5.2b53407fzoe77O&oauth_callback=
短信接口调用时报状态码404,导致点击获取验证码后,redis中没有下图中的key值:
解决:
引发新问题1
但是,手机接收到的验证码是:【国阳云】验证码:**code**,**minute**分钟内有效,请勿泄漏于他人!
没有详细的验证码code、时间minute。
引发新问题2
redis没有存入相应的key-value:
理应如此:
问题3
收到的验证码:【国阳云】验证码:**code**,**minute**分钟内有效,请勿泄漏于他人!
解决:
存入redis失败,导致原因:可能是未申请短信模板。
报异常(已解决):
突然连接不上端口6379了,
解决:
法四(+法三):
auth工程中的yml配置文件没有写redis的配置,写上:
法三(发现问题根源,但是没有解决SpringBoot整合不上redis问题——redis的配置文件redis.conf未修改,一直以来我修改的那个文件不是运行的,根据启动redis的路径显示,应该修改根路径下myredis中的redis.conf):
改三处↓
法一(依旧连不上端口):
使用阿里云服务器,在服务器中创建redis,使用服务器ip连接redis。
服务器密码有:
(root@47.96.231.143)1111aaaA、111aaA
----常用命令行----
进入bin目录:cd /usr/local/redis/redis-6.2.6/bin
启动redis:redis-server /usr/local/redis/redis-6.2.6/redis.conf
进入redis:redis-cli
修改密码:config set requirepass 1
redis-cli中使用密码:auth 1
重启redis:
查看是否启动:ps aux | grep redis-server
法二(不用redis了):
先注释掉所有和redis相关的(无效):
注释:
注释:
注释:
注释:
注释:
注释:
星图
AccessKey_Info(保存在项目父目录下):
创建的新用户:
用户登录名称:atguigu1908oss@1534177224735915.onaliyun.com
AccessKey ID:LTAI5tQULTupewjG8gwApdMn
AccessKey Secret:ulJIdPqVINIUB9jqph2XpHOwT1fVKc
0
上次进行到:
阿里支付宝开放平台-沙箱环境支付功能搭建
1、 eclipse中测试demo
https://opendocs.alipay.com/common/02kkv
https://open.alipay.com/develop/sandbox/account
修改java文件
AlipayConfig.java
package com.alipay.config;
import java.io.FileWriter; import java.io.IOException;
/* * *类名:AlipayConfig *功能:基础配置类 *详细:设置帐户有关信息及返回路径 *修改日期:2017-04-05 *说明: *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 */
public class AlipayConfig { //↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号 public static String app_id = "2021000122636028"; // 商户私钥,您的PKCS8格式RSA2私钥 public static String merchant_private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxwmNh1zeZDt7WsbFQw3/F1khqBRjUiYe/pE0aT6bJx3vqKj050KNis5R5WmIOcna9J7CL5ba4W4+GrefF8JY0oeg4pDa/hH3B6M2eM64zZkhZDuT35XVW9gzhf40Qb41urp9w0XUgixjABWr5gONSa3Q82QLmHQ/joi/wIExZdCYKwuEcY1yXMLIY3JxcZLkVcSIXksshZVJ2xkAKUhcTsJTqohdMsX2DJjNnfBueLuELSsf+g5/zAWSf/yYih/NR0A+0EMN+A5TCIFlBDdCex8gv6h66E/7LWP+IeZa7lUsfweECd3b9Yy9KTcUXJZx/SzxyM77GHOwvsnyzZSrRAgMBAAECggEAQ23VfRvCf1DgvtgQxu4h3c33EiB01sqEr/GHPd/RCOGe46i6KOA32gz82Turzock+4O+uJKeyTgSLahyLuegUr7H9BR2oBeGDLReqxA3mE/ISeAYguCLrj0PjcpwnDd0kvOyj30kzP92mAB2H8cAdBdGtH4tV1TW3iZ7wL6/t35wnTOo6lkP53TC5Hie8vkIhw3rBP1NORx1I5HQUVy6K06uyp054+eoBiVi7n7TGv8Qp4Hf81k3bEC/r3QQFKRYkl0J3Zl+MWiHyGP8OaU1WAyOccKhJ504jSDNzqB4jXjYh8jgiu+EZMboOZebCmrFkAF5Rf8OQBW89+M5rWo2QQKBgQDwjm9A8KP/S9JUpIHfGEAxq3Nh+FzaAFg9zJX8PFqUdkIBLZcjzb0NI5Pov9aJnJIRFjgc/b1yl9ysQ+DIRmVMrGucbe+2yYJBZOEYaCLxjMbEksb+syDsYCHEAxi+r9xRdbW1eqAXoOzW+qdHzEWlm7nzmqoy2T7vOXM+2G+MjQKBgQC9K+DjSn8f/1LjzkdYMD9ufiZCv6+tweyugvxdo7xS9P6ZaM7EMoe8zf02JKRxE4IVS8DT5Ivnr/6UBw/+8whw8lx0+g55isrMKr/tNqcU+di+wqad7IWV4A9J3HkdGj+bX1AsJoHTjpk5H+mACAuAztE9j6f74+3LZI28I6mAVQKBgHPBFmf5Y/EFW0C/qH7h/IHqLquGB6gfYlbai5HEuRGXlkta1M+1wLMB27q0FVhCr0bpmjbZIsBxsw5x20iEF1q93Up43FDoxnURRf+onKf/ReZxerA1IjKcT6MymkWn+ix4p+ecCk1UcqNAGh7CfQEj3mcKlbvCLjaPPpWPMJ5BAoGBAKaXJ5rCFiWlgeef1vBwvMufSC5sBFfueXJNY9VKxiQAIN4UkFHzddntRVoS9sWlul7qsUto71P/hx/xAEMy33In3Qge1fRvJ5yY4SATixxL0nZbkMNucU3FXXFcOURJw04ep8nZt2cJIyJNWiIn84lhmJ6wDe0jklwVl0Ph2/lpAoGAGCALFZPIJ1ldS9AI/5GVwCyYTZj4rYTHteIPxSRfVaYtcekAWgxrrPQbtdLkfUhB5KGYUKL8jMKoNEquhfMuSxod1XgzMfz8zTmZ6rqYN5eRPrw32HOoep9MBjHQpeZ3PPCWqcw+pmbsPuDa6XULb+adKUep2JKVBs2P5JdoTSY="; // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。 public static String alipay_public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAybCW/QK4tY9mTORuR7r9fN8/u3tGkloGD27NV2NnMc+6zEnowO5WtKpsUW8nctjS4FkE9Papr6U3Ue8gx4MIs+8lemU5W1WLiQU2f3wcnnJBbjiBNAELpLeGbSUMPv1RD8N0tzq0F0w70n+SDI5GZjF8u6E9vaqb4+keUl/kYzdKVunp+mfXV73bZTu+/TA66v9/UxMw0g8UEXL0YIpg7wXzUxBFhoCK0uxwA4A5jdF8PrtUjhIQACQzLiru5Nfi0vzYvGkOkM/4QKoJ/4WfNVZAaF0qb4DY/90PnpJH1yxL9jKHE3+L00YWdiQwq23JmTrPW8jsngrRZuyMPMbyHQIDAQAB";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 public static String notify_url = "http://kmwhgn.natappfree.cc/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 public static String return_url = "http://kmwhgn.natappfree.cc/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";
// 签名方式 public static String sign_type = "RSA2"; // 字符编码格式 public static String charset = "utf-8"; // 支付宝网关(正式网关) // public static String gatewayUrl = "https://openapi.alipay.com/gateway.do"; // 支付宝网关(沙箱网关) public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do"; // 日志路径 public static String log_path = "E:\\";
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
/** * 写日志,方便测试(看网站需求,也可以改成把记录存入数据库) * @param sWord 要写入日志里的文本内容 */ public static void logResult(String sWord) { FileWriter writer = null; try { writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis()+".txt"); writer.write(sWord); } catch (Exception e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
运行index.jsp页面→弹出http://localhost:7081/alipay.trade.page.pay-JAVA-UTF-8/index.jsp:
因为不是用的jdk1.7报错↓
2、idea中导入
application.xml
PayHandler.java中创建
① 调用支付宝接口专门封装的方法:
② returnUrlMethod方法:
③ notifyUrlMethod方法:
3、 下载沙箱版支付宝
账密↓
启动该项目步骤:
0、启动redis服务
虚拟存放位置:D:\VMware\Virtual Machines\CentOS 7 64 位用于毕设2
开启redis服务
1、启动Tomcat
访问http://localhost:7080/atcrowdfunding02_admin_webui_war_exploded/admin/to/login/page.html→后端管理员登录页面
2、启动8个微服务
访问http://localhost:4000/→前端页面
管理员账号密码: test/111
用户:C/C
沙箱版支付宝-支付密码:111111
辅助工具:
阿里云-云市场-第三方短信接口使用次数情况查看url:https://market.console.aliyun.com/?spm=5176.12818093.products-recent.dmarket.3be916d0mztg3Z#/?_k=7e6q6j
redis(可视化)
navicat(mysql可视化)
0