8. 菜单修改页面数据呈现
8.1 业务时序分析
当在菜单列表页面中选中某条记录,然后点击修改按钮时,其业务时序分析如图-16所示:
图-16
8.2 客户端关键业务及代码实现
8.2.1 列表页面修改按钮事件处理
- 业务描述与设计实现
点击页面修改按钮时,获取选中菜单记录,并异步加载编辑页面。
- 关键代码设计与实现
第一步:列表页面修改按钮事件注册,关键代码如下:
$(".input-group-btn")
.on("click",".btn-update",doLoadEditUI);
第二步:修改按钮事件处理函数定义或修改,关键代码如下:
function doLoadEditUI(){
var title;
if($(this).hasClass("btn-add")){
title="添加菜单"
}else if($(this).hasClass("btn-update")){
title="修改菜单"
//获取选中的记录数据
var rowData=doGetCheckedItem();
if(!rowData){
alert("请选择一个");
return;
}
$("#mainContentId").data("rowData",rowData);
}
var url="menu/menu_edit";
$("#mainContentId").load(url,function(){
$(".box-title").html(title);
})
}
第三步:获取用户选中记录的函数定义。关键代码如下:
function doGetCheckedItem(){
var tr=$("tbody input[type='radio']:checked").parents("tr");
return tr.data("rowData");
}
8.2.2 编辑页面菜单数据呈现
- 业务描述与设计实现
页面加载完成,在页面指定位置呈现要修改的数据。
- 关键代码设计与实现
第一步:页面加载完成以后,获取页面 div 中绑定的数据。关键代码如下:
$(function(){
…
//假如是修改
var data=$("#mainContentId").data("rowData");
if(data)doInitEditFormData(data);
});
第二步:定义编辑页面数据初始化方法。关键代码如下:
function doInitEditFormData(data){
$(".typeRadio input[value='"+data.type+"']").prop("checked",true);
$("#nameId").val(data.name);
$("#sortId").val(data.sort);
$("#urlId").val(data.url);
$("#permissionId").val(data.permission);
$("#parentId").val(data.parentName);
$("#parentId").data("parentId",data.parentId);
}
9. 菜单数据更新实现
9.1 业务时序分析
当点击编辑页面更新按钮时,其时序分析如图-17 所示:
图-17
9.2 服务端关键业务及代码实现
9.2.1 DAO 接口实现
- 业务描述与设计实现
负责将用户编辑页面提交到服务端的菜单数据,更新到数据库进行持久性存储。
- 关键代码设计与实现
在 SysMenuDao 接口中添加数据更新方法,关键代码如下:
int updateObject(SysMenu entity);
9.2.2 Mapper 映射文件定义
- 业务描述与设计实现
基于 SysMenuDao 中 updateObject 方法的定义,编写用于实现菜单更新的 SQL 元素。
- 关键代码设计与实现
在 SysMenuMapper.xml 中添加 updateObject 元素,用于更新菜单信息。关键代码如下:
<update id="updateObject"
parameterType="com.cy.pj.sys.entity.SysMenu">
update sys_menus
set
name=#{name},
type=#{type},
sort=#{sort},
url=#{url},
parentId=#{parentId},
permission=#{permission},
modifiedUser=#{modifiedUser},
modifiedTime=now()
where id=#{id}
</update>
9.2.3 Service 接口及实现
- 业务描述与设计实现
基于控制层请求,对数据进行校验并调用数据层对象将菜单信息更新到数据库中。
- 关键代码设计与实现
第一步:在 SysMenuService 接口中,添加用于更新菜单对象的方法。关键代码如下:
int updateObject(SysMenu entity);
第二步:在 SysMenuServiceImpl 类中,实现菜单保存操作。关键代码如下:
@Override
public int updateObject(SysMenu entity) {
//1.参数校验
if(entity==null)throw new IllegalArgumentException("保存对象不能为空");
//..
//2.更新菜单对象
int rows=sysMenuDao.updateObject(entity);
if(rows==0)throw new ServiceException("记录已经不存在");
return rows;
}
9.2.4 Controller 类定义
- 业务描述与设计实现
接收客户端提交的菜单数据,并对其进行封装,然后调用业务层对象进行业务处理,最后将业务层处理结果响应到客户端。
- 关键代码设计与实现
定义 Controller 方法,借助此方法处理保存菜单数据请求和响应逻辑。关键代码如下:
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysMenu entity){
sysMenuService.updateObject(entity);
return new JsonResult("update ok");
}
10. 客户端关键业务及代码实现
10.1 编辑页面更新按钮事件处理
- 业务描述与设计实现
点击页面 save 按钮时,将页面上输入的菜单编辑信息提交到服务端。
- 关键代码设计与实现
编辑 Save 按钮对应的事件处理函数。关键代码如下:
function doSaveOrUpdate(){
//1.获取表单数据
var params=doGetEditFormData();
var rowData=$("#mainContentId").data("rowData");
//2.定义 url
var insertUrl="menu/doSaveObject";
var updateUrl="menu/doUpdateObject";
var url=rowData?updateUrl:insertUrl;
if(rowData)params.id=rowData.id;
//3.异步提交数据
$.post(url,params,function(result){
if(result.state==1){
alert(result.message);
doCancel();
}else{
alert(result.message);
}
});
}
11. 总结
11.1 重难点分析
菜单管理在整个系统中的定位(资源管理)。
菜单数据的自关联查询实现(查询当前菜单以及这个菜单的上级菜单)。
菜单管理中数据的封装过程(请求数据,响应数据)。
菜单数据在客户端的呈现。(treeGrid,zTree)
11.2 FAQ 分析
菜单表是如何设计的,都有哪些字段?
菜单列表数据在客户端是如何展示的?(TreeGrid)
菜单删除业务是如何处理的?
菜单编辑页面中上级菜单数据的呈现方式?(zTree)
常用表连接方式,如图-18 所示:
图-18
11.3 BUG 分析
- 无效参数异常(IllegalArgumentException),如图-19 所示:
图-19
问题分析 :检查当前执行的业务,其结果映射配置,是否将 resultType 写成了resultMap。
- 菜单编辑页面,上级菜单树结构呈现,如图-20 所示:
图-20
问题分析:检查查询结果中是否有 parentId,或映射对象 Node 中 parentId 是否写错。
- 属性值注入失败,如图-21 所示:
图-21
问题分析:检查 Spring 容器中是否有 SysMenuService 接口的实现类对象,因为在SysMenuController 中需要一个这样的对象。
1.角色管理设计说明
1.1 业务设计说明
本模块主要实现的是企业内部角色(岗位)的管理,可以在添加角色时,为角色分配资源
访问权限,最后将角色再分配给用户,图-1 所示:
图-1
基于对表的设计,其数据逻辑关系的展示,如图-2 所示:
图-2
- 角色表设计脚本如下:
CREATE TABLE `sys_roles` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`note` varchar(500) DEFAULT NULL COMMENT '备注',
`createdTime` datetime DEFAULT NULL COMMENT '创建时间',
`modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
`createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',
`modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8 COMMENT='角色';
菜单与角色的关系表脚本设计如下:
CREATE TABLE `sys_role_menus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色 ID',
`menu_id` int(11) DEFAULT NULL COMMENT 'ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';
用户与角色关系表设计脚本如下:
CREATE TABLE `sys_user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL COMMENT '用户 ID',
`role_id` int(11) DEFAULT NULL COMMENT '角色 ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户与角色对应关系';
1.2 原型设计说明
基于用户需求,通过静态页面为用户呈现角色模块的基本需求。
当在主页点击角色管理时,呈现角色列表页面,如图-3 所示。
图-3
在列表页面点击添加按钮时,呈现角色编辑页面, 如图-4 所示.
图-4
在列表页面点击编辑按钮时,呈现角色编辑页面,如图-5 所示。
图-5
说明:假如客户对此原型进行了确认,后续则可以基于此原型进行研发。
1.3 API 设计说明
角色管理业务后台 API 分层架构及调用关系如图-6 所示:
图-6
说明:分层目的主要将复杂问题简单化,实现各司其职,各尽所能。
2.角色管理列表页面呈现
2.1 业务时序分析
角色列表页面,其加载时序分析,如图-7 所示:
图-7
2.2 服务端实现
2.2.1 Controller 实现
- 业务描述与设计实现
基于角色管理的请求业务,在 PageController 中添加返回角色页面相关方法。
- 关键代码设计与实现
检查 PageController 中是否有返回 UI 页面的方法,有则无需添加。例如:
@RequestMapping("{module}/{moduleUI}")
public String doModuleUI(@PathVariable String moduleUI) {
return "sys/"+moduleUI;
}
2.3 客户端实现
2.3.1 首页菜单事件处理
- 业务描述与设计实现
首 先 准 备 角 色 列 表 页 面 (/templates/pages/sys/role_list.html) , 然 后 在
starter.html 页面中点击菜单管理时异步加载角色列表页面。
- 关键代码设计与实现
找到项目中的 starter.html 页面,页面加载完成以后,注册菜单管理项的点击事
件,当点击角色管理时,执行事件处理函数。关键代码如下:
$(function(){
doLoadUI("load-role-id","role/role_list")
})
function doLoadUI(id,url){
$("#"+id).click(function(){
$("#mainContentId").load(url);
});
}
其中,load 函数为 jquery 中的 ajax 异步请求函数。
2.3.2 角色列表页面
- 业务描述与设计实现
本页面呈现角色信息时要以分页形式进行呈现。
- 关键代码设计与实现:
参考 sys_role.html 文件内容。
3. 角色管理列表数据呈现
3.1 数据架构分析
角色列表页面加载完成,启动角色数据异步加载操作,本次角色列表页面要以分页形式呈现角色信息,其数据查询时,数据的封装及传递过程,如图-8 所示。
图-8
说明:本模块将从数据库查询到的角色数据封装到 SysRole 对象,一行记录一个SysRole 对象。
角色数据分页查询时,其时序分析如图-9 所示:
图-9
3.2 服务端关键业务及代码实现
3.2.1 Entity 类实现
- 业务描述及设计实现
构建实体对象(POJO)封装从数据库查询到的记录,一行记录映射为内存中一个的这样的对象。对象属性定义时尽量与表中字段有一定的映射关系,并添加对应的set/get/toString 等方法,便于对数据进行更好的操作。
- 关键代码分析及实现
package com.cy.pj.sys.entity;
import java.io.Serializable;
import java.util.Date;
@Data
public class SysRole implements Serializable{
private static final long serialVersionUID = -356538509994150709L;
private Integer id;
private String name;
private String note;
private Date createdTime;
private Date modifiedTime;
private String createdUser;
private String modifiedUser;
}
说明:通过此对象除了可以封装从数据库查询的数据,还可以封装客户端请求数据,实现层与层之间数据的传递。
3.2.2 Dao 接口实现
- 业务描述及设计实现
通过数据层对象,基于业务层参数数据查询角色记录总数以及当前页面要呈现的角色信息。
- 关键代码分析及实现:
第一步:定义角色数据层接口对象,通过将此对象保证给业务层以提供角色数据操作。代码如下:
@Mapper
public interface SysRoleDao {
}
第二步:在 SysRoleDao 接口中添加 getRowCount 方法用于按条件统计记录总数。代码如下:
long getRowCount(String name);
第三步:在 SysRoleDao 接口中添加 findPageObjects 方法,基于此方法实现当前页记录的数据查询操作。代码如下:
List<SysRole> findPageObjects(String name,Long startIndex,Integer pageSize);
3.2.3 Mapper 文件实现
- 业务描述及设计实现
基于 Dao 接口创建映射文件,在此文件中通过相关元素(例如 select)描述要执行的
数据操作。
- 关键代码设计及实现
第一步:在映射文件的设计目录中添加 SysRoleMapper.xml 映射文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.pj.sys.dao.SysRoleDao">
</mapper>
第二步:在映射文件中添加 sql 元素实现,SQL 中的共性操作,代码如下:
<sql id="queryWhereId">
<where>
<if test="name!=null and name!=''">
name like concat("%",#{name},"%")
</if>
</where>
</sql>
第三步:在映射文件中添加 id 为 getRowCount 元素,按条件统计记录总数,代码如下:
<select id="getRowCount" resultType="long">
select count(*) from sys_roles
<include refid="queryWhereId"/>
</select>
第四步:在映射文件中添加 id 为 findPageObjects 元素,实现分页查询。代码如下:
<select id="findPageObjects" resultType="com.cy.pj.sys.entity.SysRole">
select * from sys_roles
<include refid="queryWhereId"/>
order by createdTime desc
limit #{startIndex},#{pageSize}
</select>
思考:
1.动态 SQL:基于用于需求动态拼接 SQL
2.SQL标签元素的作用是什么?对 SQL语句中的共性进行提取,以遍实现更好的复用.
3.Include 标签的作用是什么?引入使用 SQL标签定义的元素
3.2.4 Service 接口及实现类
- 业务描述与设计实现
在角色分页查询中,业务层对象主要负责对业务数据进行校验,并借助数据层对象完成数据的分页查询操作。
- 关键代码设计及实现
第一步:定义角色业务接口及方法,暴露外界对角色业务数据的访问,其代码参考如下:
package com.cy.pj.sys.service;
public interface SysRoleService {
PageObject<SysRole> findPageObjects(String name,Long pageCurrent);
}
第二步:定义角色业务接口实现类,并添加角色业务数据分页查询操作的具体实现,其代码参考如下:
package com.cy.pj.sys.service.impl;
import org.springframework.util.StringUtils;
@Service
public class SysRoleServiceImpl implements SysRoleService {
@Autowired
private SysRoleDao sysRoleDao;
@Override
public PageObject<SysRole> findPageObjects(String name,Long pageCurrent) {
//1.对参数进行校验
if(pageCurrent==null||pageCurrent<1)
throw new IllegalArgumentException("当前页码值无效");
//2.查询总记录数并进行校验
long rowCount=sysRoleDao.getRowCount(name);
if(rowCount==0)
throw new ServiceException("没有找到对应记录");
//3.查询当前页记录
int pageSize=3;
long startIndex=(pageCurrent-1)*pageSize;
List<SysRole> records=
sysRoleDao.findPageObjects(name,startIndex, pageSize);
//4.对查询结果进行封装并返回
return new PageObject<>(pageCurrent, pageSize,rowCount, records);
}
}
3.2.5 Controller 类实现
- 业务描述与设计实现
控制层对象主要负责请求和响应数据的处理,例如,本模块通过业务层对象执行业务逻辑,再通过 VO 对象封装响应结果(主要对业务层数据添加状态信息),最后将响应结果转换为 JSON 格式的字符串响应到客户端。
- 关键代码设计与实现
定义 Controller 类,并将此类对象使用 Spring 框架中的@RestController 注解进行标识,表示此类对象要交给 Spring 管理。然后基于@RequestMapping 注解为此类定义根路径映射。代码参考如下:
package com.cy.pj.sys.controller;
@RequestMapping("/role/")
@RestController
public class SysRoleController {
@Autowired
private SysRoleService sysRoleService;
}
在 Controller 类中添加菜单查询处理方法,代码参考如下:
@RequestMapping("doFindPageObjects")
public JsonResult doFindPageObjects( String name,Long pageCurrent) {
return new JsonResult( sysRoleService.findPageObjects(name, pageCurrent));
}