项目介绍
尚筹网项目介绍
本项目视频为B站尚硅谷的尚筹网课程,为单一架构过渡到分布式架构的项目,使用Maven管理项目,后台使用SSM框架单一架构,前台使用Springboot和SpringCloud分布式架构(其中会使用SpringSecurity做权限控制,SpringSeesion等Spring家族的产品),数据存储使用mysql,redis数据库,页面显示部分后台页面使用jsp,前台页面使用html+Thymeleaf视图解析器。
尚筹网项目构架
环境搭建
总体目标
创建工程
- 项目关系如下
- 工程创建计划
- atcrowdfunding01-admin-parent
groupId:com.atguigu.crowd
artifactId:atcrowdfunding01-admin-parent
packaging:pom
atcrowdfunding02-admin-webui
groupId:com.atguigu.crowd
artifactId:atcrowdfunding02-admin-webui
packaging:war
atcrowdfunding03-admin-component
groupId:com.atguigu.crowd
artifactId:atcrowdfunding03-admin-component
packaging:jar
atcrowdfunding04-admin-entity
groupId:com.atguigu.crowd
artifactId:atcrowdfunding04-admin-entity
packaging:jar
atcrowdfunding05-common-util
groupId:com.atguigu.crowd
artifactId:atcrowdfunding05-common-util
packaging:jar
atcrowdfunding06-common-reverse
groupId:com.atguigu.crowd
artifactId:atcrowdfunding06-common-reverse
packaging:jar
- 建立工程之间的依赖关系
- webui 依赖 component
- component 依赖 entity
- component 依赖 util
- 在IDEA模板pom中导入dependence依赖即可
创建数据库和数据库表
- 物理建模
- 第一范式:数据库表中的每一列都不可再分,也就是原子性
- 这个表中“部门”和“岗位”应该拆分成两个字段:“部门名称”、“岗位”。
- 这样才能够专门针对“部门”或“岗位”进行查询。
- 第二范式:在满足第一范式基础上要求每个字段都和主键完整相关,而不是仅和主键部分相关(主要针对联合主键而言)
- “订单详情表”使用“订单编号”和“产品编号”作为联合主键。此时“产品价格”、“产品数量”都和联合主键整体相关,但“订单金额”和“下单时间” 只和联合主键中的“订单编号”相关,和“产品编号”无关。所以只关联了主键中的部分字段,不满足第二范式。
- 把“订单金额”和“下单时间”移到订单表就符合第二范式了
- 第三范式:表中的非主键字段和主键字段直接相关,不允许间接相关
- 上面表中的“部门名称”和“员工编号”的关系是“员工编号”→“部门编号”
- →“部门名称”,不是直接相关。此时会带来下列问题:
- 数据冗余:“部门名称”多次重复出现。
- 插入异常:组建一个新部门时没有员工信息,也就无法单独插入部门 信息。就算强行插入部门信息,员工表中没有员工信息的记录同样是 非法记录。
- 删除异常:删除员工信息会连带删除部门信息导致部门信息意外丢失。
- 更新异常:哪怕只修改一个部门的名称也要更新多条员工记录。 正确的做法是:把上表拆分成两张表,以外键形式关联
- “部门编号”和“员工编号”是直接相关的。
- 第二范式的另一种表述方式是:两张表要通过外键关联,不保存冗余字段。例 如:不能在“员工表”中存储“部门名称”
建表:
CREATE DATABASE project_rowd CHARACTER SET utf8;
USE project_rowd;
drop table if exists t_admin; # 如果存在t_admin则删除存在的表
CREATE TABLE t_admin (
id INT NOT NULL auto_increment, # 主键
login_acct VARCHAR ( 255 ) NOT NULL, # 登录账号
user_pswd CHAR ( 32 ) NOT NULL, # 登录密码
user_name VARCHAR ( 255 ) NOT NULL, # 昵称
email VARCHAR ( 255 ) NOT NULL, # 邮件地址
create_time CHAR ( 19 ), # 创建时间
PRIMARY KEY ( id ) # 设置主键
);
进行基于Maven的逆向工程(根据已存在的表,在项目中逆向生成对应的实体类、Mapper文件、Mapper接口)
在reverse模块中进行逆向:
pom.xml中导入依赖
<!DOCTYPE generatorConfiguration
PUBLIC “-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN”
“http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
connectionURL=”jdbc:mysql://localhost:3306/project_crowd?serverTimezone=UTC”
userId=”root”
password=”root”>
在IDEA中进行逆向工程的方法:


<!DOCTYPE configuration PUBLIC “-//mybatis.org//DTD Config 3.0/EN”
“http://mybatis.org/dtd/mybatis-3-config.dtd">
jdbc.password=…
jdbc.Driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/project_crowd?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8 - spring-persist-mybatis.xml 整合mybatis文件 - <?xml version=”1.0” encoding=”UTF-8”?>
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.xsd”>
### 3.测试 package test; 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; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {“classpath:spring-persist-mybatis.xml”})
public class CrowdTest {
@Autowired
private DataSource dataSource; @Test
public void testConnect() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
可以打印出connection的数据,而不是报空指针异常,得出结论:整合成功。 #### 遇到的bug 无法创建sqlsessionfactory,原因是在逆向工程生成后,mybatis文件进行了移动,导致mybatis文件中namespace,resultmap中全类名错误。 ## 日志系统整合 ### 日志的意义 大量使用sysout不仅耗时,而且消耗人力,通过日志运行级别可以批量控制打印信息。



使用方法:
@Test
public void logTest(){
//获取Logger对象,这里传入的Class就是当前打印日志的类
Logger logger = LoggerFactory.getLogger(CrowdTest.class);
logger.debug(“DEBUG!!!”);
logger.info(“INFO!!!”);
logger.warn(“WARN!!!”);
logger.error(“ERROR!!!”);
} #### logback.xml实现自定义配置日志 <?xml version=”1.0” encoding=”UTF-8”?>

### 目标 在框架下通过一系列配置使spring来管理事务操作 ### 思路 xml使用配置事务的流程:


- 创建spring-persist-tx.xml文件单独进行事务配置 - (注:基于xml事务配置中,事务属性method必须配置,如果某个方法没有配置对应的txmethod,事务对方法可能不生效) <?xml version=”1.0” encoding=”UTF-8”?>
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.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd”>
- 测试: - 测试时,抛出异常,回滚不插入数据,则配置事务。 - @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {“classpath:spring-persist-mybatis.xml”,”classpath:spring-persist-tx.xml”})
public class CrowdTest {
@Autowired
private DataSource dataSource; @Autowired
private AdminMapper adminMapper; @Autowired
private AdminService adminService;
@Test
public void txTest(){
Admin admin = new Admin(null,”lily”,”321”,”丽丽”,”lily@qq.com”,null);
adminService.saveAdmin(admin);
}
} ## Spring整合SpringMVC ### 目标 1. handler中装配Service 1. 页面能够访问handler ### 思路 表述层配置文件关系

<!—配置过滤器的过滤路径,/全部路径—>
<!-- url-pattern配置方式二:配置请求扩展名 --><br /> <!-- 优点:<br /> 1.静态资源不通过SpringMVC,不需要特殊处理<br /> 2.实现伪静态效果<br /> (1)给黑客入侵增加难度<br /> (2)有利于SEO优化<br /> 缺点:不符合RESTFul风格--><br /> <url-pattern>*.html</url-pattern><br /> <!--如果一个Ajax请求扩展名时是html,但实际返回json数据,与实际不匹配,会报406错误<br /> 为了让Ajax顺利拿到json数据,配置json扩展名--><br /> <url-pattern>*.json</url-pattern><br /> </servlet-mapping>
SpringMVC配置文件具体配置
- <?xml version=”1.0” encoding=”UTF-8”?>
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.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd”>
测试
- index.jsp
- <%@ page contentType=”text/html;charset=UTF-8” language=”java” %>
<%—
base 标签必须写在 head 标签内部
base 标签必须在所有“带具体路径”的标签的前面
serverName 部分 EL 表达式和 serverPort 部分 EL 表达式之间必须写“:”
serverPort 部分 EL 表达式和 contextPath 部分 EL 表达式之间绝对不能写“/”
原因:contextPath 部分 EL 表达式本身就是“/”开头
如果多写一个“/”会干扰 Cookie 的工作机制
serverPort 部分 EL 表达式后面必须写“/”
—%>
Hello World!
测试页面 - handler
@Controller
public class TestHandler {@Autowired
AdminService adminService;@RequestMapping(“/test/ssm.html”)
public String testSSM(Model model){
Listadmins = adminService.getAll();
model.addAttribute(“admins”, admins);
return “target”;
}
}
AJAX请求
简述
@RequestBody和@RespondBody要生效需要jackson依赖,确定是否导入依赖,同时配置mvc:annotation-driven。
导入jquery
导入后记得刷新项目
Ajax测试
jsp页面
请求出现了错误QAQ
错误消息:${requestScope.exception.message}
## 管理员登录页 引入静态资源,放入webapp下

配置视图控制器,进行页面跳转
# 管理员系统 ## 管理员登录 ### 目标 识别登陆者身份,控制他行为,赋予他权限。 ### 思路

/
对明文字符进行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进制将值转化为字符串
int radix = 16;
String encoded = bigInteger.toString(radix).toUpperCase();
// 8.返回加密字符串
return encoded;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
} ### 自定义登录失败异常类 - 在exception包下创建LoginFailedException - package org.fall.exception;
/
登录失败的异常
/
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);
}
} - 修改异常处理器 - //处理登录失败异常
@ExceptionHandler(value = {LoginFailedException.class})
public ModelAndView nullLoginFailedExceptionResolver(
//实际捕获到的类型
NullPointerException nullPointerException,
//当前请求对象
HttpServletRequest request,
//当前响应对象
HttpServletResponse response
//指定普通页时去的错误页面
) throws IOException {
String viewName=”system-error”;
return conmonResolver(nullPointerException,request,response,viewName);
} - 将错误信息显示到admin-login.jsp页面 -
管理员登录
${requestScope.exception.message}
### 创建控制管理员登录的Handler方法 @Controllerpublic class AdminHandler { @Autowired
AdminService adminService; @RequestMapping(“/admin/do/login.html”)
public String doLogin(
@RequestParam(“loginAcct”) String loginAcct,
@RequestParam(“loginPswd”) String loginPswd,
HttpSession session
) {
// 调用Service方法检查登录
// 这个方法如果返回Admin对象则登录成功,如果账号密码不正确则抛出异常
Admin admin = adminService.getAdminByLoginAcct(loginAcct, loginPswd);
// 将登录成功返回的对象存入session域中
session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN, admin);
// 返回主页面
return “admin-main”;
}
} ### Service层实现业务逻辑 获取管理员的登录信息方法体
@Override
public Admin getAdminByLoginAcct(String loginAcct, String loginPswd) {
// 1.根据登录账号查询Admin对象
// 创建Example对象
AdminExample adminExample = new AdminExample();
// 创建Criteria对象
AdminExample.Criteria criteria = adminExample.createCriteria();
// 封装查找的条件
criteria.andLoginAcctEqualTo(loginAcct);
// 调用adminMapper进行查找
List
// 2.判断Admin是否为空
if (admins == null && admins.size() == 0) {
// 3.Admin对象为空则抛出异常
throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
}
// 是否出现多条数据
if (admins.size() > 1) {
throw new LoginFailedException(CrowdConstant.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
}
// 4.Admin对象不为空则取出Admin对象
Admin admin = admins.get(0);
// 5.为空抛出异常,不为空取出密码
if (admin == null) {
throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
}
String userPswdDb = admin.getUserPswd();
// 5.将表单提交的数据进行明文加密
String userPswdForm = CrowdUtils.md5(loginPswd);
// 6.对密码进行比较
if (!Objects.equals(userPswdDb, userPswdForm)) {
// 7.不相等抛出异常
throw new LoginFailedException(CrowdConstant.MESSAGE_LOGIN_FAILED);
} else {
// 8.相等则返回Admin对象
return admin;
}
} ### 重定向主页面 - 将admin-main.html页面导入后,修改base标签,和用户名 - doLogin重定向主页面,防止表单重复提交 - // 返回主页面
return “redirect:/admin/to/main/page.html”; - 配置视图控制器,重新响应页面 -
public String doLogout(HttpSession session){
// 强制session失败
session.invalidate();
return “redirect:/admin/to/login/page.html”;
} ### 抽取主页面的公共部分 - 抽取为后,需要引入jsp文件 - <%@include file=”/WEB-INF/include-head.jsp” %>
<%@include file=”/WEB-INF/include-nav.jsp” %>
<%@include file=”/WEB-INF/include-sidebar.jsp”%> - setting->Editor->File an Code Templates中创建模板方便下次使用 ## 登录检查 ### 目标 将部分资源保护起来,让没有登录的用户不能访问 ### 思路

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) {
throw new AccessForbiddenException(CrowdConstant.MESSAGE_ACCESS_FORBIDEN);
}
// 4.不为空则放行
return true;
}
} ### 自定义拒绝访问异常类 处理拦截器抛出的异常,使其返回到登录页面
public class AccessForbiddenException extends RuntimeException {
private static final long serialVersionUID = 1L; public AccessForbiddenException() {
super();
} public AccessForbiddenException(String message) {
super(message);
} public AccessForbiddenException(String message, Throwable cause) {
super(message, cause);
} public AccessForbiddenException(Throwable cause) {
super(cause);
} protected AccessForbiddenException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
} ### 配置拦截器 拦截器拦截登录页面,退出,登录请求以外的路径
## 管理员维护 ### 查询用户 #### 目标 将数据库的Admin数据在页面上已分页的形式显示,在后端将“带关键字”和“不带关键字”的分页合并为同一套代码。 #### 思路

…
PageInfo
使用pagehelper插件封装List对象为pageInfo对象
@Override
public PageInfo
// 1.开启pageHelper功能
// 体现了pageHelper的”非侵入设计“,原本要做的查询不必有任何修改
PageHelper.startPage(pageNum, pageSize);
// 2.按照关键字进行查询
List
// 3.返回封装的pageInfo对象
return new PageInfo(admins);
} #### 分页hanlder处理请求 当不点击页数是时,需要设置默认值
@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
) {
PageInfo
modelMap.addAttribute(CrowdConstant.ATTR_NAME_PAGE_INFO,pageInfo);
return “admin-page”;
} #### 在分页页面显示信息 - 先导入jstl标签库,进行调用信息显示 - <%@taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core“ %> - table标签显示数据 -
<%— jstl —%>
# | 账号 | 名称 | 邮箱地址 | 操作 | |
---|---|---|---|---|---|
抱歉,查不到相关的数据! | |||||
${status.count} | ${admin.loginAcct} | ${admin.userName} | ${admin.email} | class=”btn btn-success btn-xs”> class=”btn btn-primary btn-xs”> class=”btn btn-danger btn-xs”> |
<%—引入基于jquery的paginationjs—%>
- 分页导航条显示 -
- js代码 - - 修复造成一直刷新的回调bug - 在jquery.pagination.js中将其注释掉 - // 回调函数
// opts.callback(current_page, this); #### 关键字查询 - 修改form标签 - style=”float:left;”>
value=”${param.keyword}”/>
- 在页面切换后,关键字不在请求参数中,在转发请求路径中加入关键字 - window.location.href = “admin/get/page.html?pageNum=” + pageNum+”&keyword=”+${param.keyword}; ### 删除用户 #### 目标 点击删除将数据删除后,返回当前页面。 #### 思路

class=”btn btn-danger btn-xs”> #### Hander方法 返回时的问题: 1. 直接返回页面会因为没有发送分页请求而无法显示数据 1. 使用转发发送分页请求可以实现页面的显示,但用户刷新后会造成后台重复删除 1. 使用重定向可以防止重复删除 @RequestMapping(“/admin/remove/{adminId}/{pageNum}/{keyword}.html”)
public String remove(
@PathVariable(“adminId”) Integer adminId,
@PathVariable(“pageNum”) Integer pageNum,
@PathVariable(“keyword”) String keyword
){
adminService.removeOne(adminId);
return “redirect:/admin/get/page.html?pageNum=”+pageNum+”&keyword=”+keyword;
} #### AdminService接口 void removeOne(Integer adminId); #### AdminServiceImpl @Override
public void removeOne(Integer adminId) {
adminMapper.deleteByPrimaryKey(adminId);
} ### 新增用户 #### 目标 将表单提交的admin对象保存到数据库中 - loginAcct不能重复 - 密码加密 #### 思路

ALTER TABLE t_admin ADD UNIQUE INDEX(login_acct); #### 修改admin-add.jsp页面 修改字段中的name属性和Admin实体对应,修改提交请求路径
填充表格
使用jquery处理json数据,并填充在表格中
// 执行分页,生成分页效果
function generatePage() {
// 通过getPageInfoRemote()方法得到pageInfo
var pageInfo = getPageInfoRemote();
// 将pageInfo传入fillTableTBody()方法,在tbody中生成分页后的数据<br /> fillTableTBody(pageInfo);<br />}
// 从远程服务器端获取PageInfo数据
function getPageInfoRemote() {
// 调用$.ajax()函数发送请求,并用ajaxResult接收函数返回值<br /> var ajaxResult = $.ajax({<br /> url: "role/get/page/info.json",<br /> type: "post",<br /> // 页码、页大小、关键字均从全局变量中获取<br /> data: {<br /> "pageNum": window.pageNum,<br /> "pageSize": window.pageSize,<br /> "keyword": window.keyword<br /> },<br /> async: false, //关闭异步模式,使用同步,这是为了显示页面时保持现有的顺序<br /> dataType: "json"<br /> });
// 取得当前的响应状态码<br /> var statusCode = ajaxResult.status;
// 判断当前状态码是不是200,不是200表示发生错误,通过layer提示错误消息<br /> if (statusCode != 200) {<br /> layer.msg("失败!状态码=" + statusCode + "错误信息=" + ajaxResult.statusText);<br /> return null;<br /> }
// 响应状态码为200,进入下面的代码<br /> // 通过responseJSON取得handler中的返回值<br /> var resultEntity = ajaxResult.responseJSON;
// 从resultEntity取得result属性<br /> var result = resultEntity.result;
// 判断result是否是FAILED<br /> if (result == "FAILED") {<br /> // 显示失败的信息<br /> layer.msg(resultEntity.message);<br /> return null;<br /> }
// result不是失败时,获取pageInfo<br /> var pageInfo = resultEntity.data;
// 返回pageInfo<br /> return pageInfo;<br />}
// 根据PageInfo填充表格
function fillTableTBody(pageInfo) {
// 清除tbody中的旧内容<br /> $("#rolePageTBody").empty();
// 使无查询结果时,不显示导航条<br /> $("#Pagination").empty();
// 判断pageInfo对象是否有效,无效则表示未查到数据<br /> if (pageInfo == null || pageInfo == undefined || pageInfo.list == null || pageInfo.list.length == 0) {<br /> $("#rolePageTBody").append("<tr><td colspan='4' align='center'>抱歉!没有查询到想要的数据</td></tr>");<br /> return;<br /> }
// pageInfo有效,使用pageInfo的list填充tbody<br /> for (var i = 0; i < pageInfo.list.length; i++) {
var role = pageInfo.list[i];<br /> var roleId = role.id;<br /> var roleName = role.name;<br /> var numberTd = "<td>" + (i + 1) + "</td>";<br /> var checkboxTd = "<td><input type='checkbox'/></td>";<br /> 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>"
// 拼接三个小按钮成一个td<br /> var buttonTd = "<td>" + checkBtn + " " + pencilBtn + " " + removeBtn + "</td>";
// 将所有的td拼接成tr<br /> var tr = "<tr>" + numberTd + checkboxTd + roleNameTd + buttonTd + "</tr>";
// 将拼接后的结果,放入id=rolePageTBody<br /> $("#rolePageTBody").append(tr);<br /> }
// 调用generateNavigator()方法传入pageInfo,进行生成分页页码导航条<br /> generateNavigator(pageInfo);
}
// 生成分页页码导航条
function generateNavigator(pageInfo) {
//获取分页数据中的总记录数<br /> var totalRecord = pageInfo.total;
//声明Pagination设置属性的JSON对象<br /> var properties = {<br /> num_edge_entries: 3, //边缘页数<br /> num_display_entries: 5, //主体页数<br /> callback: paginationCallback, //点击各种翻页反扭时触发的回调函数(执行翻页操作)<br /> current_page: (pageInfo.pageNum - 1), //当前页码<br /> prev_text: "上一页", //在对应上一页操作的按钮上的文本<br /> next_text: "下一页", //在对应下一页操作的按钮上的文本<br /> items_per_page: pageInfo.pageSize //每页显示的数量<br /> };
// 调用pagination()函数,生成导航条<br /> $("#Pagination").pagination(totalRecord, properties);
}
// 翻页时的回调函数
function paginationCallback(pageIndex, jQuery) {
// pageIndex是当前页码的索引,因此比pageNum小1<br /> window.pageNum = pageIndex + 1;
// 重新执行分页代码<br /> generatePage();
// 取消当前超链接的默认行为<br /> return false;
}
乱码问题
- 方法一:可以修改tomcat启动参数
- 方法二:将js文件修改为带有BOM的utf-8格式
- 方法三:在引入js时规定字符编码charset=”UTF-8”
关键字查询
目标
把页面上的“查询”表单和已经封装好的执行分页的函数连起来即可
思路
绑定单击事件
// 给查询按钮绑定单击事件
$(“#searchBtn”).click(function () {
// 查询后的页面从第一页显示
window.pageNum = 1;
// 获取关键字数据给对应的全局变量
window.keyword = $(“#inputKeyword”).val();
// 调用分页函数刷新
generatePage();
});
新增角色
目标
通过在打开的模态框中输入角色名称,执行对新角色的保存
思路
前端
新增模态框的引入
- 创建model-role-add.jsp
- <%@ page contentType=”text/html;charset=UTF-8” language=”java” %>
- 将静态资源在页尾引入
- <%@include file=”/WEB-INF/model-role-add.jsp” %>
- 绑定新增按钮,弹出静态框
- // 点击新增按钮打开模态框
$(“#showAddModalBtn”).click(function () {
$(“#addRoleModal”).modal(“show”);
});
绑定保存按钮
点击保存按钮,返送Ajax异步请求
// 给新增模态框的保存按钮绑定单击事件
$(“#saveRoleBtn”).click(function () {
// 获取用户在文本框中输入角色的名称
// #addModal表示找到整个模态框
// 空格表示后代元素中继续查找
// [name=roleName] 表示匹配name属性roleName的元素
var roleName = $.trim($(“#addRoleModal [name=roleName]”).val());
// 发送Ajax请求<br /> $.ajax({<br /> url: "role/save.json",<br /> type: "post",<br /> data: {<br /> name: roleName<br /> },<br /> success: function (response) {<br /> var result = response.result;<br /> // 成功则弹框输出<br /> if (result == "SUCCESS") {<br /> layer.msg("操作成功!");
// 进入最后一页 方便显示添加的内容<br /> window.pageNum = 999;<br /> // 重新生成分页<br /> generatePage();<br /> }
// 失败弹出原因<br /> if (result == "FAILED") {<br /> layer.msg("操作失败!" + response.message);<br /> }<br /> },<br /> error: function () {<br /> layer.msg(response.status + " " + response.statusText);<br /> }<br /> });
// 关闭模态框<br /> $("#addRoleModal").modal("hide");
// 清理模态框<br /> $("#addRoleModal [name=roleName]").val("");<br />});
后端
handler方法
@ResponseBody
@RequestMapping(“/role/save.json”)
public ResultEntity
roleService.saveRole(role);
return ResultEntity.successWithData();
}
RoleService的实现
@Override
public void saveRole(Role role) {
roleMapper.insert(role);
}
更新角色
目标
修改角色信息
思路
前端
修改模态框的引入
- modal-role-update.jsp页面
<%@ page contentType=”text/html;charset=UTF-8” language=”java” pageEncoding=”UTF-8” %>
- 引入到role-page中
- <%@include file=”/WEB-INF/modal-role-update.jsp”%>
回显数据
- 在动态生成的编辑按钮处添加选择器,以及添加id值,方便修改时获取id的值
- var pencilBtn = ““
- 给动态生成的编辑按钮绑定单击事件,弹出静态框,并回显信息在输入文本框中
- 普通单击事件无法绑定翻页后的编辑按钮,使用on函数利用静态元素取绑定动态生成的按钮
// 传统的事件绑定方式只有那个在第一个页面有效,在翻页后失效,使用jQuery对象on()函数解决
//首先找到“动态生成”的元素附着的“静态”元素
// on:第一个参数:事件类型
// on:第二个参数:找到真正的绑定元素的选择器
// on:第一个参数:事件的响应函数
$(“#rolePageTBody”).on(“click”,”.pencilBtn”,function () {
// 打开模态框
$(“#editModal”).modal(“show”);// 获取表格中当前行的角色名称
var roleName=$(this).parent().prev().text();// 获取当前角色的id,为了发送Ajax请求,将它设置为全局变量
window.roleId=this.id;// 使用roleName设置模态框中的文本框
$(“#editModal [name=roleName]”).val(roleName);// 绑定更新,发送Ajax请求
$(“#updateRoleBtn”).click(function () {
// 获取用户填写的更新用户名
$(“#editModal [name=roleName]”).val();
发送Ajax修改数据
最后不执行清除模态框,(需要回显信息),不返回最后一页
// 绑定更新,发送Ajax请求
$(“#updateRoleBtn”).click(function () {
// 从模态框的文本框中获得修改后的roleName
var roleName = $(“#editModal [name=roleName]”).val();
$.ajax({
url: “role/update.json”,
type: “post”,
data: {
id:window.roleId, // 从全局遍历取得当前角色的id
name:roleName
},
dataType: “json”,
success:function (response) {
if (response.result == “SUCCESS”){
layer.msg(“操作成功!”);
generatePage();
}
if (response.result == “FAILED”)
layer.msg(“操作失败”+response.message)
},
error:function (response) {
layer.msg(“statusCode=”+response.status + “ message=”+response.statusText);
}
});
// 关闭模态框<br /> $("#editModal").modal("hide");<br /> });<br /> });
后端
Hander方法
@ResponseBody
@RequestMapping(“/role/update.json”)
public ResultEntity
roleService.updateRole(role);
return ResultEntity.successWithData();
}
RoleService实现
@Override
public void updateRole(Role role) {
roleMapper.updateByPrimaryKey(role);
}
删除角色
目标
前端的“单条删除”和“批量删除”在后端合并为同一套操作,合并的依据是:单条删除时id也被放在数组中,后端完全根据id的数组进行删除
思路
前端
删除模态框的引入
- modal-role-confirm.jsp模态框
<%@ page contentType=”text/html;charset=UTF-8” language=”java” %>
- 引入到role-page页面
- <%@include file=”/WEB-INF/modal-role-confirm.jsp”%>
确认删除框的函数
显示已选择的角色的角色名,封装函数在my-role.js中
// 打开确认删除的模态框
function showConfirmModal(roleArray) {
// 显示模态框
$(“#confirmRoleModal”).modal(“show”);
// 清除旧的模态框中的数据<br /> $("#confirmList").empty();
// 创建一个全局变量数组,用于存放要删除的roleId<br /> window.roleIdArray = [];
// 填充数据<br /> for (var i = 0; i < roleArray.length; i++) {
var roleId = roleArray[i].id;
// 将当前遍历到的roleId放入全局变量<br /> window.roleIdArray.push(roleId);
var roleName = roleArray[i].name;
// 显示出要删除的数据<br /> $("#confirmList").append(roleName + "<br/>");<br /> }
点击删除弹出模态框(单个删除)
- 在每页的删除按钮添加id(方便后面用来删除操作)和class的属性
- var removeBtn = ““
- 给单击X删除绑定单击事件
- // 给单击删除把昂顶单击响应函数
$(“#rolePageTBody”).on(“click”, “.removeBtn”, function () {
// 通过x按钮删除时,只有一个角色,因此只需要建一个特殊的数组,存放单个对象即可
var roleArray = [{
id: this.id,
name: $(this).parent().prev().text()
}];
// 调用删除静态框函数,传入roleArray
showConfirmModal(roleArray);
});
绑定确认删除单击事件
需要先转换为json字符串,后台才能使用请求体获取请求数据
// 给确认删除按钮绑定单击事件
$(“#confirmRoleBtn”).click(function () {
// 将id信息封装到请求体
var requestBody = JSON.stringify(window.roleIdArray);
$.ajax({
url: “role/remove.json”,
type: “post”,
data: requestBody, // 将转换后的数据传给后端
dataType: “json”,
contentType: “application/json;charset=UTF-8”, // 表明发送json格式数据
success: function (response) {
if (response.result == “SUCCESS”) {
layer.msg(“操作成功!”);
generatePage();
}
if (response.result == “FAILED”)
layer.msg(“操作失败” + response.message)
},
error: function (response) {
layer.msg(“statusCode=” + response.status + “ message=” + response.statusText);
}
});
// 关闭模态框<br /> $("#confirmRoleModal").modal("hide");<br />});
多选框删除角色(多个删除)
- 在外部js生成表单td绑定id,方便多选框勾取时获取id值
- var checkboxTd = ““;
- 给全选框设置id(id=summaryBox)
#
名称
操作- 多选框全选,全不选处理
- // 全选,全不选的反向操作
$(“#rolePageTBody”).on(“click”, “.itemBox”, function () {
// 获取当前已选中的多选框的数量
var checkedBoxCount = $(“.itemBox:checked”).length;
// 获取全部checkBox的数量
var checkBoxAll = $(“.itemBox”).length;
// 两者比较设置总的多选框状态
$(“#summaryBox”).prop(“checked”, checkedBoxCount == checkBoxAll);
});
// 给多选删除按钮绑定单击事件
$(“#batchRemoveBtn”).click(function (){
// 创建一个数组对象,用来存放后面获得的角色对象<br /> var roleArray = [];
// 遍历被勾选的内容<br /> $(".itemBox:checked").each(function () {<br /> // 通过this引用当前遍历得到的多选框的id<br /> var roleId = this.id;
// 通过DOM操作获取角色名称<br /> var roleName = $(this).parent().next().text();
roleArray.push({<br /> "id":roleId,<br /> "name":roleName<br /> });<br /> });
- 点击删除多选框内容
- 调用显示静态框函数,传入roleArray
// 给多选删除按钮绑定单击事件
$(“#batchRemoveBtn”).click(function () {// 创建一个数组对象,用来存放后面获得的角色对象<br /> var roleArray = []; // 遍历被勾选的内容<br /> $(".itemBox:checked").each(function () {<br /> // 通过this引用当前遍历得到的多选框的id<br /> var roleId = this.id; // 通过DOM操作获取角色名称<br /> var roleName = $(this).parent().next().text(); roleArray.push({<br /> id: roleId,<br /> name: roleName<br /> });<br /> }); // 判断roleArray的长度是否为0<br /> if (roleArray.length == 0) {<br /> layer.msg("请至少选择一个来删除");<br /> return;<br /> } // 显示确认框<br /> showConfirmModal(roleArray);
后台
Handler方法
传入的是json数据,使用请求体来获取json字符串
@ResponseBody
@RequestMapping(“/role/update.json”)
public ResultEntity
roleService.updateRole(role);
return ResultEntity.successWithoutData();
}
RoleService的实现
@Override
public void deleteRole(List
RoleExample roleExample = new RoleExample();
RoleExample.Criteria criteria = roleExample.createCriteria();
criteria.andIdIn(roleIdList);
roleMapper.deleteByExample(roleExample);
}
菜单维护
树形结构基础知识
整个树形结构最多只能有三级
在数据库中表示树形结构
创建菜单数据库表
创建菜单的数据库表
create table t_menu
(
id int(11) not null auto_increment,
pid int(11),
name varchar(200),
url varchar(200),
icon varchar(200),
primary key (id)
);
插入数据
插入数据
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘1’,NULL,’ 系统权限菜单’,’glyphicon glyphicon-th-list’,NULL);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘2’,’1’,’ 控 制 面 板 ‘,’glyphicon glyphicon-dashboard’,’main.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘3’,’1’,’权限管理’,’glyphicon glyphicon glyphicon-tasks’,NULL);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘4’,’3’,’ 用 户 维 护 ‘,’glyphicon glyphicon-user’,’user/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘5’,’3’,’ 角 色 维 护 ‘,’glyphicon glyphicon-king’,’role/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘6’,’3’,’ 菜 单 维 护 ‘,’glyphicon glyphicon-lock’,’permission/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘7’,’1’,’ 业 务 审 核 ‘,’glyphicon glyphicon-ok’,NULL);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘8’,’7’,’ 实名认证审核’,’glyphicon glyphicon-check’,’auth_cert/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘9’,’7’,’ 广 告 审 核 ‘,’glyphicon glyphicon-check’,’auth_adv/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘10’,’7’,’ 项 目 审 核 ‘,’glyphicon glyphicon-check’,’auth_project/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘11’,’1’,’ 业 务 管 理 ‘,’glyphicon glyphicon-th-large’,NULL);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘12’,’11’,’ 资 质 维 护 ‘,’glyphicon glyphicon-picture’,’cert/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘13’,’11’,’ 分 类 管 理 ‘,’glyphicon glyphicon-equalizer’,’certtype/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘14’,’11’,’ 流 程 管 理 ‘,’glyphicon glyphicon-random’,’process/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘15’,’11’,’ 广 告 管 理 ‘,’glyphicon glyphicon-hdd’,’advert/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘16’,’11’,’ 消 息 模 板 ‘,’glyphicon glyphicon-comment’,’message/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘17’,’11’,’ 项 目 分 类 ‘,’glyphicon glyphicon-list’,’projectType/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘18’,’11’,’ 项 目 标 签 ‘,’glyphicon glyphicon-tags’,’tag/index.htm’);
insert into t_menu
(id
, pid
, name
, icon
, url
) values(‘19’,’1’,’ 参 数 管 理 ‘,’glyphicon glyphicon-list-alt’,’param/index.htm’);
关联方式
子节点通过pid字段关联到父节点的id字段,建立父子关系。
根节点的pid为空
在Java类中表示树形结构
基本方式
在 Menu 类中使用 List children 属性存储当前节点的子节点。
为了配合zTree 所需要添加的属性
- pid 属性:找到父节点
- name 属性:作为节点名称
- icon 属性:当前节点使用的图标
- open 属性:控制节点是否默认打开
- url 属性:点击节点时跳转的位置
页面显示树形结构
目标
将数据库中查询得到的数据到页面上显示出来
思路
数据库查询全部→Java 对象组装→页面上使用 zTree 显示
逆向工程
- 修改配置文件
- 修改Menu实体类
- 此处省略get,set,有参,无参构造器
- package crowd.entity;
import java.util.ArrayList;
import java.util.List;
public class Menu {
// 主键
private Integer id;
// 父节点id
private Integer pid;
// 结点名称
private String name;
// 结点附带的url地址,是将来点击菜单项时要跳转的路径
private String url;
// 结点图标样式
private String icon;
// 存储节点的集合,初始化是为了避免空指针异常
private List