1 用户管理设计说明
1.1 业务设计说明
本模块主要是实现对用户信息的管理,包括用户查询,保存,更新,禁用启用等操作,其 业务分析如下图所示:图
基于对表的设计,其数据逻辑关系的展示,如图
用户表设计的脚本如下:
DROP TABLE IF EXISTS `sys_users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `sys_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`salt` varchar(50) DEFAULT NULL COMMENT '盐 密码加密时前缀,使加密后的值不同',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(100) DEFAULT NULL COMMENT '手机号',
`valid` tinyint(4) DEFAULT NULL COMMENT '状态 0:禁用 1:正常 默认值 :1',
`deptId` int(11) DEFAULT NULL,
`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`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 COMMENT='系统用户';
/*!40101 SET character_set_client = @saved_cs_client */;
用户与角色的关系表脚本设计如下:
DROP TABLE IF EXISTS `sys_user_roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
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=77 DEFAULT CHARSET=utf8 COMMENT='用户与角色对应关系';
/*!40101 SET character_set_client = @saved_cs_client */;
1.2 原型设计说明
基于用户需求,通过静态页面为用户呈现用户模块的基本需求。 当在主页点击用户管理时,呈现用户列表页面,如图:
在列表页面点击添加按钮时,呈现用户编辑页面, 如图:
在编辑页面点击所属部门时,呈现部门树结构信息, 如图:
说明:假如客户对此原型进行了确认,后续则可以基于此原型进行研发。
1.3 API 设计说明
用户管理业务后台 API 分层架构及调用关系如图
说明:分层目的主要将复杂问题简单化,实现各司其职,各尽所能。
2 用户管理列表页面呈现
2.1 业务时序分析
用户列表页面,其加载时序分析,如图
2.2 服务端实现
2.2.1 Controller 实现
业务描述与设计实现
基于用户管理的请求业务,在 PageController 中添加返回用户页面相关方法。
关键代码设计与实现
检查 PageController 中是否有返回 UI 页面的方法,有则无需添加。例如:
@RequestMapping("{module}/{moduleUI}")
public String doMouduleUI(@PathVariable String module){
return "sys/"+moduleUI;
}
2.3 客户端实现
2.3.1 首页菜单事件处理
业务描述与设计实现
首先准备用户列表页面(/templates/pages/sys/user_list.html),然后在 starter.html 页面中点击用户管理时异步加载用户列表页面。
关键代码设计与实现
找到项目中的 starter.html 页面,页面加载完成以后,注册用户管理项的点击事件, 当点击用户管理时,执行事件处理函数。关键代码如下:
$(function(){
doLoadUI("load-user-id","user/user_list") })
function doLoadUI(id,url){
$("#"+id).click(function(){
$("#mainContentId").load(url);
});
}
其中,load 函数为 jquery 中的 ajax 异步请求函数。
2.3.2 用户列表页面
业务描述与设计实现
本页面呈现用户信息时要以分页形式进行呈现。
▪ 关键代码设计与实现:
参考 sys_user.html 文件内容
3 用户管理列表数据呈现
3.1 数据架构分析
用户列表页面加载完成,启动用户数据的异步加载操作,本次列表页面要以分页形式 呈现用户信息,其数据查询时,数据的封装及传递过程,如图
说明:本模块将从数据库查询到的用户及相关数据封装到SysUserDeptVo 对象,一行 记录一个SysUserDeptVo 对象。 用户列表数据加载,其时序分析,如图
3.2 服务端关键业务及代码实现
3.2.1 pojo 类实现
业务描述及设计实现
构建pojo对象,封装从数据库查询到的用户以及用户对应的部门信息,一行记录映射 为内存中一个的这样的对象。
关键代码分析及实现
定义SysUserDept类,基于此类对象属性封装数据。关键代码如下:
package com.cy.pj.sys.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUserDept implements Serializable {
private static final long serialVersionUID = 3776304095517793129L;
private Integer id;
private String username;
private String password;//md5
private String salt;//加密盐
private String email;
private String mobile;
private Integer valid=1;
private SysDept sysDept; //private Integer deptId;
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
private Date createdTime;
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
private Date modifiedTime;
private String createdUser;
private String modifiedUser;
}
说明:通过此对象除了可以封装从数据库查询的数据,还可以封装客户端请求数据, 实现层与层之间数据的传递。
3.2.2 Dao 接口实现
业务描述及设计实现
通过数据层对象,基于业务层的参数数据,查询用户记录总数以及当前页面要呈现的 用户基本信息。
关键代码分析及实现:
第一步:定义用户数据层接口对象,通过此对象实现用户业务的数据操作。
第二步:在 SysUserDao 接口中添加 getRowCount 方法用于按条件统计记录总数。
第三步:在SysUserDao 接口中添加findPageObjects 方法,基于此方法实现当前页 记录的数据查询操作。代码如下:
package com.cy.pj.sys.dao;
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.pojo.SysUserDept;
import java.util.List;
/**通过此Dao操作用户模块的数据*/
@Mapper
public interface SysUserDao {
int getRowcount(String username);
List<SysUserDept> findPageObject(String username,Integer startIndedx,Integer pageSize);
}
3.2.3 Mapper 文件实现
业务描述及设计实现
基于 Dao 接口创建映射文件,在此文件中通过相关元素(例如 select)描述要执行的 数据操作。
关键代码设计及实现
第一步:在映射文件的设计目录中添加 SysUserMapper.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.SysUserDao">
</mapper>
第二步:在映射文件中添加 sql 元素实现,SQL 中的共性操作,代码如下:
<sql id="queryWhereId">
from sys_users
<where>
<if test="username!=null and username!=''">
username like concat("%",#{username},"%")
</if>
</where>
</sql>
第三步:在映射文件中添加 id 为 getRowCount 元素,按条件统计记录总数,代码如 下:
<select id="getRowCount" resultType="int">
select count(*)
<include refid="queryWhereId"/>
</select>
第四步:在映射文件中添加 id 为 findPageObjects 元素,实现分页查询。代码如下:
<select id="findPageObjects" resultMap="sysUserDept">
select *
<include refid="queryWhereId"/>
order by createdTime desc
limit #{startIndex},#{pageSize}
</select>
第五步:定义查询结果映射元素。代码如下:
<resultMap type="com.cy.pj.sys.vo.SysUserDeptVo" id="sysUserDept">
<!-- 一般应用于 many2one 或 one2one 做关联查询
在当前应用是基于 deptId 查询部门信息并将其
存储到 SysUserDept 对象的 sysDept 属性中。-->
<association property="sysDept" column="deptId" select="com.cy.pj.sys.dao.SysDeptDao.findById">
</association>
</resultMap>
思考:
- resultMap 用户进行自定义结果映射。
- association 元素用于定义关联数据的查询。
3.2.4 Service 接口及实现类
业务描述与设计实现
在用户分页查询中,业务层对象主要负责对业务数据进行校验,并借助数据层对象完 成数据的分页查询操作。
▪ 关键代码设计及实现
第一步:定义用户业务接口及方法,暴露外界对用户业务数据的访问,其代码参考如下:
package com.cy.pj.sys.service;
public interface SysUserService {
PageObject<SysUserDept> findPageObjects(String username,Integer pageCurrent);
}
第二步:定义用户业务接口实现类,并添加用户业务数据分页查询操作的具体实现,其 代码参考如下:
package com.cy.pj.sys.service.impl; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserDao sysUserDao;
@Override
public PageObject<SysUserDept> findPageObjects( String username,Integer pageCurrent) {
//1.对参数进行校验
if(pageCurrent==null||pageCurrent<1) throw new IllegalArgumentException("当前页码值无效");
//2.查询总记录数并进行校验
int rowCount=sysUserDao.getRowCount(username);
if(rowCount==0) throw new ServiceException("没有找到对应记录");
//3.查询当前页记录
int pageSize=2;
int startIndex=(pageCurrent-1)*pageSize;
List<SysUserDept> records= sysUserDao.findPageObjects(username,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("/user/")
@RestController
public class SysUserController {
@Autowired
private SysUserService sysUserService;
@RequestMapping("doFindPageObjects")
public JsonResult doFindPageObjects(String username,Integer pageCurrent) {
return new JsonResult(sysUserService.findPageObjects(username,pageCurrent));
}
}
3.3 客户端关键业务及代码实现
3.3.1 菜单列表信息呈现
业务描述与设计实现
角色分页页面加载完成以后,向服务端发起异步请求加载角色信息,当角色信息加载 完成需要将角色信息、分页信息呈现到列表页面上。
关键代码设计与实现
异步请求处理函数,关键代码如下: 第一步:分页页面加载完成,向服务端发起异步请求,代码参考如下:
$(function(){
//为什么要将 doGetObjects 函数写到 load 函数对应的回调内部。
$("#pageId").load("doPageUI",function(){
doGetObjects();
});
}
第二步:定义异步请求处理函数,代码参考如下:
function doGetObjects(){
//debugger;//断点调试
//1.定义 url 和参数
var url="user/doFindPageObjects"
var params={"pageCurrent":1};//pageCurrent=2
//2.发起异步请求
//请问如下 ajax 请求的回调函数参数名可以是任意吗?可以,必须符合标识符 的规范
$.getJSON(url,params,function(result){
//请问 result 是一个字符串还是 json 格式的 js 对象?对象
doHandleResponseResult(result);
} );//特殊的 ajax 函数
}
第三步:定义回调函数,处理服务端的响应结果。代码如下:
function doHandleResponseResult (result){ //JsonResult
if(result.state==1){//ok
//更新 table 中 tbody 内部的数据
doSetTableBodyRows(result.data.records);//将数据呈现在页面上
//更新页面 page.html 分页数据
doSetPagination(result.data); //此方法写到 page.html 中
}else{
alert(result.msg);
}
}
第四步:将异步响应结果呈现在 table 的 tbody 位置。代码参考如下:
function doSetTableBodyRows(records){
//1.获取 tbody 对象,并清空对象
var tBody=$("#tbodyId");
tBody.empty();
//2.迭代 records 记录,并将其内容追加到 tbody
for(var i in records){
//2.1 构建 tr 对象
var tr=$("<tr></tr>");
//2.2 构建 tds 对象
var tds=doCreateTds(records[i]);
//2.3 将 tds 追加到 tr 中
tr.append(tds);
//2.4 将 tr 追加到 tbody 中
tBody.append(tr);
}
}
第五步:创建每行中的 td 元素,并填充具体业务数据。代码参考如下:
function doCreateTds(row){
console.log(row);
var tds="<td><input type='radio' name='radioId' value='"+row.id+"' ></td>"+
"<td>"+row.username+"</td>"+
"<td>"+(row.sysDept?row.sysDept.name:'未分配')+"</td>"+
"<td>"+row.email+"</td>"+
"<td>"+row.mobile+"</td>"+
"<td>"+(row.valid?"启用":"禁用")+"</td>"+
"<td>"+new Date(row.createdTime).toLocaleString()+"</td>"+
"<td>"+new Date(row.modifiedTime).toLocaleString()+"</td>"+
"<td><button type='button' class='btn btn-default btnvalid'>"+(row.valid?"禁用":"启用")+"</button></td>";
return tds;
}
4 用户管理禁用操作实现
4.1 核心业务分析
基于用户在列表页面上选择的的用户记录 ID,执行禁用或启用操作,后续业务中被禁 用的用户不允许登陆系统。 其时序分析,如图:
4.2 服务端关键业务及代码实现
4.2.1 Dao 接口实现
业务描述及设计实现
基于用户 id,修改用户状态信息,对此用户进行禁用或启用。
关键代码设计及实现:
在创建 SysUserDao 中添加修改用户状态信息的方法。关键代码如下:
int validById(@Param("id") Integer id,
@Param("valid") Integer valid,
@Param("modifiedUser") String modifiedUser);
4.2.2 Mapper 文件实现
业务描述及设计实现
在 SysUserDao 接口对应的映射文件中添加修改用户状态信息的元素,然后在元素内 部定义具体的 SQL 实现。
关键代码设计与实现
在 SysUserMapper.xml 文件,添加修改用户状态的 SQL 元素定义,关键代码如下:
<update id="validById">
update sys_users
set
valid=#{valid},modifiedUser=#{modifiedUser},modifiedTime=now()
where id=#{id}
</update>
4.2.3 Service 接口及实现类
业务描述与设计实现
在用户业务层,添加用于完成禁用或启用用户状态的相关业务方法及实现。
关键代码设计与实现
第一步:在 SysUserService 接口中,添加修改用户装填的方法。关键代码如下:
int validById(Integer id,Integer valid)
第三步:在 SysUserServiceImpl 实现类中添加禁用,启用业务的具体实现。关键代 码如下:
@Override
public int validById(Integer id,Integer valid) {
//1.合法性验证
if(id==null||id<=0) throw new ServiceException("参数不合法,id="+id);
if(valid==null||(valid!=1&&valid!=0)) throw new ServiceException("参数不合法,valid="+valid);
//2.执行禁用或启用操作(admin 为后续登陆用户)
int rows=sysUserDao.validById(id, valid,"admin");
//3.判定结果,并返回
if(rows==0) throw new ServiceException("此记录可能已经不存在");
return rows;
}
4.2.4 Controller 类实现
业务描述与设计实现
在用户控制层对象中,添加用于处理禁用启用业务的控制层方法。首先在此方法中通 过形参接收客户端提交的数据,然后调用业务层对象执行禁用,启用操作,最后封装执行 结果,并在运行时将响应对象转换为 JSON 格式的字符串,响应到客户端。
关键代码设计与实现
第一步:在 SysUserController 中添加用于执行删除业务的方法。代码如下:
@RequestMapping("doValidById")
public JsonResult doValidById(Integer id,Integer valid){
sysUserService.validById(id,valid);
return new JsonResult("update ok");
}
第二步:启动 tomcat 进行访问测试,打开浏览器输入如下网址:
http://localhost/user/doValidById?id=10
4.3 客户端关键业务及代码实现
4.3.1 用户列表页面事件处理
业务描述及设计实现
用户在用户列表页面上点击禁用或按钮,将用户记录 id 异步提交到服务端,最后在服 务端执行用户的禁用、启用动作。
关键代码设计与实现
第一步:用户列表页面加载完成以后,在删除按钮上进行点击事件注册。关键代码如 下:
$(".input-group-btn").on("click",".btn-valid",doValidById)
第二步:定义禁用、启用操作对应的事件处理函数。关键代码如下:
function doValidById(){
//1.获取对象 id,validId 的值
var btn=$(this);//this 执行调用 dom 对象
var user=btn.parents("tr").data("rowData");
var id=user.id;
var valid=user.valid;
//2.构建 url,参数对象
var url="user/doValidById";
var params={"id":id,"valid":valid?0:1}
//3.发送异步请求,更新数据
$.post(url,params,function(result){
if(result.state==1){
alert(result.message);
//doGetObjects();//一种刷新方式
doEditRow(btn,valid?0:1);//一种局部刷新方式
}else{
alert(result.message);
}
})
}
第三步:定义禁用、启用操作对应的局部刷新处理函数。关键代码如下:
function doEditRow(btn,valid){
//1.修改按钮上内容
btn.html(valid?"禁用":"启用");
//2.修改 td 中元素内容
var tr=btn.parents("tr");
tr.find("td:eq(5)").html(valid?"启用":"禁用");
//3.修改并重新绑定数据
var sysUser=tr.data("sysUser");
sysUser.valid=valid;
tr.data("sysUser",sysUser);
}
5 用户添加页面呈现
5.1 业务时序分析
在用户列表页面,点击添加按钮时加载编辑页面,其加载时序分析,如图:
5.2 准备用户编辑页面
准备用户编辑页面(/templates/pages/sys/user_edit.html)
5.3 用户编辑页面呈现
业务描述与设计实现
在用户列表页面中点击添加按钮时,呈现用户编辑页面。
关键代码设计与实现
第一步:用户列表事件注册,关键代码如下:
$(document).ready(function(){
$(".input-group-btn").on("click",".btn-add",doLoadEditUI);
});
第二步:定义用户列表页面添加按钮的事件处理函数,关键代码如下:
function doLoadEditUI(){
//1.定义标题(添加或修改)
var title;
if($(this).hasClass("btn-add")){
title="用户添加";
}else{
title="用户修改";
}
//2.异步加载页面
var url="user/user_edit";
$("#mainContentId").load(url,function(){
$(".box-title").html(title);
});
};
6 用户页面角色呈现
6.1 核心业务分析
用户编辑页面呈现以后,发起异步任务从服务端获取角色信息然后呈现在页面上,其时 序分析如图:
6.2 服务端关键业务及代码实现
6.2.1 pojo 类定义
业务描述与设计实现
定义 pojo对象,基于此对象封装用户角色信息。
关键代码设计与实现
创建 CheckBox 类,基于此类对象封装角色 id,角色名信息。关键代码如下:
package com.cy.pj.common.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**通过此对象封装类似CheckBox的结构的数据*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CheckBox implements Serializable {
private static final long serialVersionUID = -3930756932197466333L;
private Integer id;
private String name;
}
6.2.2 Dao 接口定义
业务描述与设计实现
在角色数据层接口中添加查询角色相关信息的方法,并对查询结果进行封装。
关键代码设计与实现
在 SysRole 添加查询角色 id,name 的相关方法。关键代码如下:
List<CheckBox> findObjects();
6.2.3 Mapper 文件实现
业务描述与设计实现
在角色数据层接口对应的映射文件中,定义角色查询对应的映射元素。
关键代码设计与实现
在 SysRoleMapper.xml 中添加 findObjects 元素。关键代码如下:
<select id="findObjects" resultType="com.cy.pj.common.pojo.CheckBox">
select id,name
from sys_roles
</select>
6.2.4 Service 接口及实现类
业务描述与设计实现
在角色业务层接口中添加查询角色 id,name 相关信息的业务方法。
关键代码设计与实现
第一步:在 SysRoleService 接口中添加 findObjects 方法。关键代码如下:
List<CheckBox> findObjects();
第二步:在 SysRoleService 接口中添加 findObjects 方法实现。关键代码如下:
List<CheckBox> findObjects();
第二步:在 SysRoleService 接口中添加 findObjects 方法实现。关键代码如下:
@Override
public List<CheckBox> findObjects() {
return sysRoleDao.findObjects();
}
6.2.5 Controller 类实现
业务描述与设计实现
在角色控制层对象中添加查询角色 id,name 相关信息的业务方法。
关键代码设计与实现
在 SysRoleController 中添加 doFindRoles 方法。关键代码如下:
@RequestMapping("doFindRoles")
public JsonResult doFindRoles() {
return new JsonResult(sysRoleService.findObjects());
}
6.3 客户端关键业务及代码实现
6.3.1 用户编辑页面实现
业务描述与设计实现
在用户编辑页面加载完成以后,异步加载角色信息,并在页面上进行呈现。
关键代码设计与实现
第一步:页面加载完成以后,异步加载角色相关信息。关键代码如下:
$(document).ready(function(){doLoadSysRoles(); }
第二步:定义异步加载角色信息的方法。关键代码如下:
function doLoadSysRoles(){
var url="role/doFindObjects";
$.getJSON(url,function(result){
if(result.state==1){
//初始化角色信息
doInitDivSysRoles(result.data);
}else{
alert(result.message);
}
})
};
第三步:定义页面初始化方法,完成页面角色信息的初始化操作。关键代码如下:
//初始化表单角色数据
function doInitDivSysRoles(data){
var div=$("#rolesId");
var checkBox="<input type='checkbox' name='roleItem' value='[id]'>[name]";
for(var i in data){
div.append(
checkBox.replace("[id]",data[i].id).replace("[name]",data[i].name));
}
}
7 用户数据添加实现
7.1 核心业务分析
用户在编辑页面点击保存按钮时,获取编辑页面用户输入的基本信息异步提交到服务 端,实现用户数据的持久化操作。其时序分析,如图:
7.2 服务端关键业务及代码实现
7.2.1 pojo 类定义
业务描述与设计实现
负责封装用户的基本信息,然后由数据层持久化到数据库。
关键代码设计与实现
定义 SysUser 类,并通过相关数据封装用户基本信息,关键代码如下:
package com.cy.pj.sys.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUser implements Serializable {
private static final long serialVersionUID = 3776304095517793129L;
private Integer id;
private String username;
private String password;//md5
private String salt;//加密盐
private String email;
private String mobile;
private Integer valid=1;
private Integer deptId;
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
private Date createdTime;
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
private Date modifiedTime;
private String createdUser;
private String modifiedUser;
}
7.2.2 DAO 接口定义
业务描述与设计实现
负责将用户提交的用户基本信息,持久化到数据库。
关键代码设计与实现
在 SysUserDao 接口中定义数据持久化方法:
int insertObject(SysUser entity);
在 SysUserRoleDao 接口中方法定义(不存在则创建)
int insertObjects(@Param("userId")Integer userId, @Param("roleIds")Integer[] roleIds);
7.2.3 Mapper 映射文件定义
业务描述与设计实现
基于 SysUserDao 中方法的定义,在对应的映射文件中添加的对应 SQL 元素。用于将 用户信息添加到数据库。
关键代码设计与实现
第一步:在 SysUserMapper.xml 中添加 insertObject 元素,用于写入用户信息。关 键代码如下:
<insert id="insertObject"
parameterType="com.cy.pj.sys.pojo.SysUser"
useGeneratedKeys="true"
keyProperty="id">
insert into sys_users
(username,password,deptId,email,mobile,salt,valid,
createdTime,modifiedTime,createdUser,modifiedUser)
values
(#{username},#{password},#{deptId},#{email},#{mobile},#{salt},#{valid},
now(),now(),#{createdUser},#{modifiedUser})
</insert>
第二步:在 SysUserRoleMapper 中元素定义,关键代码如下:
<insert id="insertObjects">
insert into sys_user_roles
(user_id,role_id)
values <!--(1,2),(1,3),(1,4)-->
<foreach collection="roleIds" separator="," item="roleId">
(#{userId},#{roleId})
</foreach>
</insert>
7.2.4 Service 接口定义及实现
业务描述与设计实现
基于控制层请求,调用数据层对象将用户以及对应的角色信息写入到数据库中。
关键代码设计与实现
第一步:在 SysUserService 接口中,添加用于保存用户对象的方法。关键代码如下:
int saveObject(SysUser entity,Integer[]roleIds);
第二步:在 SysUserServiceImpl 类中,实现用户保存操作。关键代码如下:
@Override
public int saveObject(SysUser entity, Integer[] roleIds) {
long start=System.currentTimeMillis();
log.info("start:"+start);
//1.参数校验
if(entity==null) throw new IllegalArgumentException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getUsername())) throw new IllegalArgumentException("用户名不能为空");
if(StringUtils.isEmpty(entity.getPassword())) throw new IllegalArgumentException("密码不能为空");
if(roleIds==null || roleIds.length==0) throw new IllegalArgumentException("至少要为用户分配角色");
//2.保存用户自身信息
//2.1 对密码进行加密
String source=entity.getPassword();
String salt=UUID.randomUUID().toString();
SimpleHash sh=new SimpleHash(//Shiro 框架
"MD5",//algorithmName 算法
source,//原密码
salt, //盐值
1);//hashIterations 表示加密次数
entity.setSalt(salt);
entity.setPassword(sh.toHex());//将加密结果转换为16进制
int rows=sysUserDao.insertObject(entity);
//3.保存用户角色关系数据
sysUserRoleDao.insertObjects(entity.getId(), roleIds);
long end=System.currentTimeMillis();
log.info("end:"+end);
log.info("total time :"+(end-start));
//4.返回结果
return rows;
}
说明:使用 SimpleHash 时,要添加一个 shiro 框架依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
7.2.5 Controller 类定义
业务描述与设计实现
接收客户端提交的用户相关数据,并对其进行封装,然后调用业务层对象进行业务处 理,最后将业务层处理结果响应到客户端。
关键代码设计与实现
定义 Controller 方法,借助此方法处理保存用户数据的请求和响应逻辑。关键代码 如下:
@RequestMapping("doSaveObject")
public JsonResult doSaveObject(SysUser entity,Integer[] roleIds){
sysUserService.saveObject(entity,roleIds);
return new JsonResult("save ok");
}
7.3 客户端关键业务及代码实现
7.3.1 用户编辑页面部门信息呈现
业务描述与设计实现
用户点击所属部门时,异步加载部门信息并以 zTree 结构进行呈现,然后用户可以选 择对应的部门,将部门相关信息更新到页面上。
关键代码设计与实现
第一步:在所有部门相关 dom 元素上进行事件注册,关键代码如下:
$(".form-horizontal").on("click",".load-sys-dept",doLoadZTreeNodes);
第二步:定义事件处理函数,用户呈现部门信息,关键代码如下:
function doLoadZTreeNodes(){
var url="dept/doFindZTreeNodes";
$("#treeLayer").css("display","block");
$.getJSON(url,function(result){
if(result.state==1){
zTree = $.fn.zTree.init($("#zTreeId"),setting,result.data);
}else{
alert(result.message);
}
});
}
第三步:部门 div 容器中注册,确定和取消事件,关键代码如下: 页面加载完成以后,进行事件注册:
$("#treeLayer").on("click",".btn-cancel",doHideTree).on("click",".btn-confirm",doConfirm);
确定按钮事件处理函数定义:
function doConfirm(){
//1.获取选中的记录(id,name);
var selectedNodes=zTree.getSelectedNodes();
var node=selectedNodes[0];
//2.将 id 和 name 填写或绑定在具体对象上
$("#deptId").val(node.name);
$("#deptId").data("deptId",node.id)
//3.隐藏 zTree 对应的 Div
doHideTree();
}
取消按钮事件处理函数定义:
function doHideTree(){ $("#treeLayer").css("display","none"); }
7.3.2 页面 cancel 按钮事件处理
业务描述与设计实现
点击页面 cancel 按钮时,加载菜单那列表页面。
关键代码设计与实现
第一步:事件注册(页面加载完成以后)
$(".box-footer").on("click",".btn-cancel",doCancel)
第二步:事件处理函数定义
function doCancel(){
$("#mainContentId").removeData("rowData");
$("#mainContentId").load("user/user_list");
}
7.3.3 页面 Save 按钮事件处理
业务描述与设计实现
点击页面 save 按钮时,将页面上输入的菜单信息提交到服务端。
关键代码设计与实现
第一步:事件注册(页面加载完成以后)。
$(".box-footer").on("click",".btn-save",doSaveOrUpdate)
第二步:Save 按钮事件处理函数定义。关键代码如下:
function doSaveOrUpdate(){
//1.url
var insertUrl="user/doSaveObject";
//2.获取表单数据
var params=doGetEditFormData();
//3.发起异步请求
$.post(insertUrl,params,function(result){
if(result.state==1){
alert(result.message);
doCancel();
}else{
alert(result.message);
}
})
}
第三步:表单数据获取及封装。关键代码如下:
function doGetEditFormData(){
var params={
"username":$("#usernameId").val(),
"password":$("#passwordId").val(),
"email":$("#emailId").val(),
"mobile":$("#phoneId").val(),
"deptId":$("#deptId").data("deptId"),
}
var roleIds=new Array();
$("#rolesId input[type='checkbox']").each(function(){
if($(this).prop("checked")){
roleIds.push($(this).val())
}
});
params.roleIds=roleIds.toString();
console.log(params);
return params;
}
8 用户修改页面数据呈现
8.1 核心业务分析
基于用户 id 查询用户以及用户对应的部门和角色信息,并将其对应的信息呈现在用户 编辑页面上,其时序图分析如图:
8.2 服务端关键业务及代码实现
8.2.1 DAO 接口定义
业务描述与设计实现
负责基于 id 执行用户和角色信息数据的查询操作。
关键代码设计与实现
在 SysUserDao 接口中定义基于用户 id 查询用户相关信息的方法,关键代码如下:
SysUserDept findObjectById(Integer id);
在 SysUserRoleDao 接口中定义基于用户 id 查询角色 id 信息的方法,关键代码如下:
List<Integer> findRoleIdsByUserId(Integer id);
8.2.2 Mapper 文件定义
业务描述与设计实现
基于SysUserDao,SysUserRoleDao中方法的定义,在映射文件中添加对应的用户查 询元素。
关键代码设计与实现
第一步:在 SysUserMapper.xml 中添加 id 为 findObjectById 的 select 元素,关 键代码如下:
<select id="findObjectById" parameterType="int" resultMap="sysUserDept">
select * from sys_users
where id=#{id}
</select>
第二步:在SysUserRoleMapper.xml中添加id为findRoleIdsByUserId的select 元素,关键代码如下:
<select id="findRoleIdsByUserId" resultType="int">
select role_id from sys_user_roles
where user_id=#{id}
</select>
8.2.3 Service 接口定义及实现
业务描述与设计实现
基于控制层请求,调用数据层方法,查询对应的用户及相关信息。
关键代码设计与实现
第一步:在 SysUserService 接口中,添加基于 id 查询用户及相关信息的方法。关 键代码如下:
Map<String,Object> findObjectById(Integer userId) ;
第二步:在 SysUserService 接口对应的实现类 SysUserServiceImpl 中添加 findObjectById 的具体实现。关键代码如下:
@Override
public Map<String, Object> findObjectById(Integer userId) {
//1.合法性验证
if(userId==null||userId<=0) throw new IllegalArgumentException("参数数据不合法,userId="+userId);
//2.业务查询
SysUserDept user = sysUserDao.findObjectById(userId);
if(user==null) throw new ServiceException("此用户已经不存在");
List<Integer> roleIds= sysUserRoleDao.findRoleIdsByUserId(userId);
//3.数据封装
Map<String,Object> map=new HashMap<>();
map.put("user", user);
map.put("roleIds", roleIds);
return map;
}
8.2.4 Controller 类定义
业务描述与设计实现
基于客户端请求,调用业务层方法,查询对应的用户及相关信息。
关键代码设计与实现
在 SysUserController 类中定义基于用户 ID 查询用户的相关方法。关键代码如下:
@RequestMapping("doFindObjectById")
public JsonResult doFindObjectById(Integer id){
Map<String,Object> map= sysUserService.findObjectById(id);
return new JsonResult(map);
}
8.3 客户端关键业务及代码实现
8.3.1 列表页面修改按钮事件处理
业务描述与设计实现
在用户列表修改按钮上进行事件注册,点击页面修改按钮时,基于用户 id 向服务端发 起异步请求获取用户相关数据,然后加载修改页面。
关键代码设计与实现
第一步:页面加载完成,进行修改按钮事件注册,关键代码如下:
$(function(){
//假如是修改
$(".input-group-btn") .on("click","btn-update",doLoadEditUI);
});
第二步:修改按钮事件处理函数定义或修改,关键代码如下:
function doLoadEditUI(){
//1.判定点击的对象
var title;
if($(this).hasClass("btn-add")){
title="添加用户";
doLoadPage(title);
}else if($(this).hasClass("btn-update")){
title="修改用户";
var id=$("tbody input[name='radioId']:checked").val();
console.log("id="+id)
if(!id){
alert("请先选择");
return;
}
//基于 id 进行查询并加载编辑页面
doFindObjectById(id,title);
}
}
第三步:定义或修改加载编辑页面的方法。关键代码如下:
function doLoadPage(title){
var url="user/user_edit"
$("#mainContentId").load(url,function(){
$(".box-title").html(title);
})
}
第四步:定义基于 id 查询用户信息的方法。关键代码如下:
function doFindObjectById(id,title){
//1.params
var params={"id":id};
//2.url
var url="user/doFindObjectById";
//3.ajax request
$.getJSON(url,params,function(result){//JsonResult
if(result.state==1){
$("#mainContentId").data("rowData",result.data);
doLoadPage(title);
}else{
alert(result.message);
}
});
}
8.3.2 编辑页面角色数据呈现
业务描述与设计实现
页面加载完成,获取编辑页面数据,然后在页面指定位置进行数据呈现数据。
关键代码设计与实现
第一步:在用户编辑页面中,角色数据加载完成以后,获取用户编辑页面中需要的表 单数据,然后进行页面数据初始化。关键代码如下:
function doLoadRoles(){
var url="role/doFindRoles";
$.getJSON(url,function(result){
if(result.state==1){
doInitPageRoles(result.data);
doInitFormData();//修改时
}else{
alert(result.message);
}
})
}
第三步:定义编辑页面数据初始化方法。关键代码如下:
function doInitFormData(){
var data=$("#mainContentId").data("rowData");
if(!data)return;
$("#pwdDiv").remove();
console.log(data);
//初始化用户信息
$("#usernameId").val(data.user.username);
$("#deptId").val(data.user.sysDept?data.user.sysDept.name:'');
$("#deptId").data("deptId",data.user.sysDept?data.user.sysDept.id:'');
$("#emailId").val(data.user.email);
$("#phoneId").val(data.user.mobile);
//初始化用户角色信息
var ids=data.roleIds;
for(var i in ids){
$("#rolesId input[value='"+ids[i]+"']").prop("checked",true);
}
}
9 用户数据更新实现
9.1 核心业务分析
在用户编辑页面点击更新按钮时,异步提交数据到服务端,服务端要更新用户自身信 息以及用户和角色关系数据,其时序图分析,如图:
9.2 服务端关键业务及代码实现
9.2.1 DAO 接口实现
业务描述与设计实现
获取用户编辑页面数据,然后异步提交到服务端,将用户信息以及用户对应的角色关 系数据更新到数据库。
关键代码设计与实现
第一步:在 SysUserDao 接口中添加数据更新方法,关键代码如下:
int updateObject(SysUser entity);
第二步:在 SysUserRoleDao 接口中添加基于用户 id 删除关系数据的方法,关键代 码如下:
int deleteObjectsByUserId(Integer userId);
9.2.2 Mapper 文件定义
业务描述与设计实现
基于SysUserDao,SysUserRoleDao中方法的定义,编写用于实现用户更新的SQL元 素。
关键代码设计与实现
第一步:在 SysUserMapper.xml 中添加 updateObject 元素,用于更新菜单信息。 关键代码如下:
<update id="updateObject" parameterType="com.cy.pj.sys.pojo.SysUser">
update sys_users
set username=#{username},
mobile=#{mobile},
email=#{email},
deptId=#{deptId},
modifiedTime=now(),
modifiedUser=#{modifiedUser}
where id=#{id}
</update>
第二步:在 SysUserRoleMapper.xml 文件中添加基于用户 id 删除关系数据的元素, 关键代码如下:
<delete id="deleteObjectsByUserId" parameterType="int">
delete from sys_user_roles
where user_id=#{userId}
</delete>
9.2.3 Service 接口及实现
业务描述与设计实现
基于控制层请求,对数据进行校验并调用数据层对象将角色信息以及角色菜单关系数 据更新到数据库中。
关键代码设计与实现
第一步:在 SysUserService 接口中,添加用于更新角色对象的方法。关键代码如下:
int updateObject(SysUser entity,Integer[] roleIds)
第二步:在 SysUserServiceImpl 类中,实现更新角色操作。关键代码如下:
@Override
public int updateObject(SysUser entity, Integer[] roleIds) {
//1.参数校验
if(entity==null)
throw new IllegalArgumentException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getUsername()))
throw new IllegalArgumentException("用户名不能为空");
if(roleIds==null||roleIds.length==0)
throw new IllegalArgumentException("必须为用户分配角色");
//3.保存用户自身信息
int rows=sysUserDao.updateObject(entity);
//4.保存用户和角色关系数据
sysUserRoleDao.deleteObjectsByUserId(entity.getId());
sysUserRoleDao.insertObjects(entity.getId(),roleIds);
return rows;
}
9.2.4 Controller 类定义
业务描述与设计实现
首先接收客户端提交的用户数据,并对其进行封装,然后调用业务层对象对角色信息 进行更行更新,最后将业务层处理结果响应到客户端。
关键代码设计与实现
在 SysUserController 类中定义更新角色的方法。关键代码如下:
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysUser entity,Integer[] roleIds){
sysUserService.updateObject(entity,roleIds);
return new JsonResult("update ok");
}
9.3 客户端关键业务及代码实现
9.3.1 编辑页面更新按钮事件处理
业务描述与设计实现
点击页面 save 按钮时,将页面上输入的用户编辑信息异步提交到服务端进行更新。
关键代码设计与实现
修改用户编辑页面中保存表单数据的 JS 函数,关键代码如下:
function doSaveOrUpdate(){
//1.params
var rowData=$("#mainContentId").data("rowData");
var params=doGetEditFormData();
if(rowData){
params.id=rowData.user.id;
}
//1.url
var insertUrl="user/doSaveObject";
var updateUrl="user/doUpdateObject";
var url=rowData?updateUrl:insertUrl;
//2.获取表单数据
//3.发起异步请求
$.post(url,params,function(result){
if(result.state==1){
alert(result.message);
doCancel();
}else{
alert(result.message);
}
})
}
10 修改密码页面呈现
10.1 服务端关键业务设计及实现
检查 PageController 中是否有返回 UI 页面的方法,有则无需添加。例如:
@RequestMapping("{module}/{moduleUI}")
public String doModuleUI(@PathVariable String moduleUI) {
return "sys/"+moduleUI;
}
10.2 客户端关键业务设计及实现
10.2.1 准备密码编辑页面
准备密码编辑页面(/templates/pages/sys/pwd_edit.html)
10.2.2 密码编辑页面呈现
业务描述与设计实现
在系统首页左侧操作菜单中点击修改密码时,呈现密码编辑页面。
关键代码设计与实现
在 starter.html 页面尾部的页面加载完成的事件处理函数中添加事件处理,关键代 码如下:
$(function(){
doLoadUI("load-user-id","user/user_list")
})
function doLoadUI(id,url){
$("#"+id).click(function(){
$("#mainContentId").load(url);
});
}
11 密码修改页面数据持久化实现
11.1 服务端关键业务设计及实现
11.1.1 DAO 接口定义
业务描述及设计实现
基于用户 id,修改用户密码和盐值。
关键代码设计及实现:
在创建 SysUserDao 中添加修改用户密码信息的方法。关键代码如下:
int updatePassword(@Param("password")String password,@Param("salt")String salt,@Param("id")Integer id);
11.1.2 Mapper 映射文件定义
业务描述及设计实现
基于用户 SysUserDao 中修改密码方法的定义,在映射文件中定义映射元素。
关键代码设计及实现:
在创建 SysUserMapper.xml 中定义修改密码对应的映射元素。关键代码如下:
<update id="updatePassword">
update sys_users
set password=#{password},
salt=#{salt},
modifiedTime=now()
where id=#{id}
</update>
11.1.3 Service 接口定义及实现
业务描述及设计实现
基于控制层提交的用户相关信息,实现修改密码业务。
关键代码设计及实现:
第一步:在 SysUserService 接口中添加,用于实现密码修改业务的方法(原密码,新密码,确认密码)。关键代码 如下:
int updatePassword(String password,String newPassword,String cfgPassword);
第二步:在 SysUserService 接口的实现类 SysUserServiceImpl 中添加密码修改业 务的具体实现。关键代码如下:
@Override
public int updatePassword(String password, String newPassword, String cfgPassword) {
//1.判定新密码与密码确认是否相同
if(StringUtils.isEmpty(newPassword)) throw new IllegalArgumentException("新密码不能为空");
if(StringUtils.isEmpty(cfgPassword)) throw new IllegalArgumentException("确认密码不能为空");
if(!newPassword.equals(cfgPassword)) throw new IllegalArgumentException("两次输入的密码不相等");
//2.判定原密码是否正确
if(StringUtils.isEmpty(password)) throw new IllegalArgumentException("原密码不能为空");
//获取登陆用户
SysUser user=(SysUser)SecurityUtils.getSubject().getPrincipal();
SimpleHash sh=new SimpleHash("MD5", password, user.getSalt(), 1);
if(!user.getPassword().equals(sh.toHex())) throw new IllegalArgumentException("原密码不正确");
//3.对新密码进行加密
String salt=UUID.randomUUID().toString();
sh=new SimpleHash("MD5",newPassword,salt, 1);
//4.将新密码加密以后的结果更新到数据库
int rows=sysUserDao.updatePassword(sh.toHex(), salt,user.getId());
if(rows==0) throw new ServiceException("修改失败");
return rows;
}
11.1.4 Controller 类定义
业务描述及设计实现
基于客户端提交的修改密码请求,定义处理请求的相关方法及映射。
关键代码设计及实现:
在 SysUserController 类中添加用于实现密码修改的控制层方法。关键代码如下:
@RequestMapping("doUpdatePassword")
public JsonResult doUpdatePassword(String pwd,String newPwd, String cfgPwd) {
sysUserService.updatePassword(pwd, newPwd, cfgPwd);
return new JsonResult("update ok");
}
11.2 客户端关键业务设计及实现
业务描述及设计实现
获取修改页面表单中用户填写的数据,然后向服务端发起异步请求进行数据更新。
关键代码设计及实现:
第一步:对保存和取消按钮进行事件注册,关键代码如下:
$(function(){
$(".box-footer")
.on("click",".btn-cancel",doCancel)
.on("click",".btn-save",doUpdateObject)
});
第二步:定义保存和取消事件处理函数,关键代码如下:
function doCancel(){
$("#mainContentId").html("");
}
function doUpdateObject(){
//1.获取用户提交数据
var params={pwd:$("#pwdId").val(),
newPwd:$("d").val(),
cfgPwd:$("#cfgPwdId")#newPwdI.val()
//2.定义请求的 url
var url="user/doUpdatePassword"
//3.异步提交请求,执行更新操作
$.post(url,params,function(result){
alert(result.message);
})
}
12 总结
12.1 重难点分析
▪ 一对多数据的保存?(保存用户信息,保存用户和角色的关系数据)
▪ 多对一数据的查询映射?(查询用户时查询用户对应的部门信息)。
▪ 用户密码的加密实现?(对用户密码进行MD5盐值加密)
12.2 FAQ 分析
▪ 用户与角色之间是什么关系?(Many2Many)
▪ 描述一下用户查询业务的实现?
▪ 描述一下用户添加业务的实现?
▪ 描述一下用户更新业务的实现?
▪ resultMap元素应用场景?
1)表中字段名与内存中的映射对象属性不一致(set方法不匹配)
2)表关联查询映射,表嵌套查询映射。