文章目录

github:https://github.com/LawssssCat/learn-springboot-quick


1 用户管理设计说明

1.1 业务设计说明

本模块主要是实现对用户信息的管理,包括用户查询,保存,更新,禁用启用等操作,其业务分析如下图所示:图-1所示:

图-1
基于对表的设计,其数据逻辑关系的展示,如图-2所示:

图-2
用户表设计的脚本如下:

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 '状态',
  `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=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 原型设计说明

基于用户需求,通过静态页面为<mark>用户呈现用户</mark>模块的基本需求。
当在主页点击用户管理时,呈现用户列表页面,如图-3所示。

  • 有部门名称,根据id 多表查询
  • 分页查询

    图-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/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 数据架构分析
用户列表页面加载完成,启动用户数据的异步加载操作,本次列表页面要以分页形式呈现用户信息,其数据查询时,数据的封装及传递过程,如图-8所示。

图-8
说明:本模块将从数据库查询到的用户及相关数据封装到SysUserDeptVo对象,
一行记录一个 SysUserDeptVo 对象。
用户列表数据加载,其时序分析,如图-9下:

图-9

3.2 服务端关键业务及代码实现

3.2.1 Vo类实现

业务描述及设计实现
构建值对象(VO)封装从数据库查询到的用户以及用户对应的部门信息,一行记录映射为内存中一个的这样的对象。
关键代码分析及实现
定义SysUserDeptVo类,基于此类对象属性封装数据。关键代码如下:

package com.cy.pj.sys.vo;
@Data //lombok
public class SysUserDeptVo implements Serializable{
	private static final long serialVersionUID = 5477389876913514595L;
	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;
	private Date createdTime;
	private Date modifiedTime;
	private String createdUser;
	private String modifiedUser;
}

说明:通过此对象除了可以封装从数据库查询的数据,还可以封装客户端请求数据,实现层与层之间数据的传递。

3.2.2 Dao接口实现

业务描述及设计实现
通过数据层对象,基于业务层的参数数据,查询用户记录总数以及当前页面要呈现的用户基本信息。
关键代码分析及实现:
第一步:定义用户数据层接口对象,通过此对象实现用户业务的数据操作。代码如下:

@Mapper
public interface SysUserDao {
}

第二步:在SysUserDao接口中添加 getRowCount 方法用于按条件统计记录总数。代码如下:

	int getRowCount(@Param("username") String username);

第三步:在SysUserDao接口中添加 findPageObjects 方法,基于此方法实现当前页记录的数据查询操作。代码如下:

	List<SysUserDeptVo> findPageObjects(
			      @Param("username")String  username,
			      @Param("startIndex")Integer startIndex,
			      @Param("pageSize")Integer pageSize);

说明:

  1. 当DAO中方法参数多余一个时尽量使用@Param注解进行修饰并指定名字,然后再Mapper文件中便可以通过类似#{username}方式进行获取,
    否则只能通过#{arg0},#{arg1}或者#{param1},#{param2}等方式进行获取。
  2. 当DAO方法中的参数应用在动态SQL中时无论多少个参数,尽量使用@Param注解进行修饰并定义。

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="sysUserDeptVo">
           select *
           <include refid="queryWhereId"/>
           order by createdTime desc
           limit #{startIndex},#{pageSize}
   </select>

第五步:定义查询结果映射元素。代码如下:

   <resultMap type="com.cy.pj.sys.vo.SysUserDeptVo" id="sysUserDeptVo">
            <!-- 一般应用于many2one或one2one做关联查询 在当前应用是基于deptId查询部门信息并将其 存储到SysUserDeptVo对象的sysDept属性中。 -->
            <association property="sysDept" column="deptId" select="com.cy.pj.sys.dao.SysDeptDao.findById">
            </association>  
   </resultMap>

思考:

  1. resultMap 用户进行自定义结果映射。
  2. association元素用于定义关联数据的查询。

3.2.4 Service接口及实现类

业务描述与设计实现
在用户分页查询中,业务层对象主要负责对业务数据进行校验,并借助数据层对象完成数据的分页查询操作。
关键代码设计及实现
第一步:定义用户业务接口及方法,暴露外界对用户业务数据的访问,其代码参考如下:

package com.cy.pj.sys.service;
public interface SysUserService {
	 PageObject<SysUserDeptVo> 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 {

	//可以自己写个配置类
	
	@Autowire
	private SysUserDao sysUserDao ; 

	@Override
	public PageObject<SysUserDeptVo> 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<SysUserDeptVo> 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;
@RestController
@RequestMapping("/user/")
public class SysUserController {
}

在Controller类中添加菜单查询处理方法,代码参考如下:

@RequestMapping("doFindPageObjects")
public JsonResult doFindPageObjects(
			 String username,Integer pageCurrent) {
		 return new JsonResult(
			sysUserService.findPageObjects(name,
					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 btn-valid'>"+(row.valid?"禁用":"启用")+"</button></td>"; 
       return tds;
   }

文章目录


4 用户管理禁用操作实现

4.1 核心业务分析

基于用户在列表页面上选择的的用户记录ID,执行禁用或启用操作,后续业务中被禁用的用户不允许登陆系统。 其时序分析,如图-10下:

图-10

4.2 服务端关键业务及代码实现

4.2.1 Dao接口实现

业务描述及设计实现
基于用户id,修改用户状态信息,对此用户进行禁用或启用。
关键代码设计及实现:
在创建SysUserDao中添加修改用户状态信息的方法。关键代码如下:

	int validById(
			@Param("id")Integer id,
			@Param("valid")Integer valid,
			@Param("modifiedUser")String modifiedUser);
4.2.2Mapper文件实现
业务描述及设计实现
在SysUserDao接口对应的映射文件中添加修改用户状态信息的元素,然后在元素内部定义具体的SQL实现。
关键代码设计与实现
在SysUserMapper.xml文件,添加修改用户状态的SQL元素定义,关键代码如下:
<update id="validById">
       update sys_users
       set valid=#{valid},
           modifiedUser=#{modifiedUser},
           modifiedTime=now()
       where id=#{id}
   </update>
4.2.3Service接口及实现类
业务描述与设计实现
在用户业务层,添加用于完成禁用或启用用户状态的相关业务方法及实现。
关键代码设计与实现
第一步:在SysUserService接口中,添加修改用户装填的方法。关键代码如下:
int validById(Integer id,Integer valid,String modifiedUser)
第三步:在SysUserServiceImpl实现类中添加禁用,启用业务的具体实现。关键代码如下:
@Override
	public int validById(Integer id,Integer valid,
String modifiedUser) {
		//1.合法性验证
		if(id==null||id<=0)
		throw new ServiceException("参数不合法,id="+id);
		if(valid!=1&&valid!=0)
		throw new ServiceException("参数不合法,valie="+valid);
		if(StringUtils.isEmpty(modifiedUser))
		throw new ServiceException("修改用户不能为空");
		//2.执行禁用或启用操作
		int rows=sysUserDao.validById(id, valid, modifiedUser);
		//3.判定结果,并返回
		if(rows==0)
		throw new ServiceException("此记录可能已经不存在");
		return rows;
	}

4.2.4 Controller类实现

业务描述与设计实现
在用户控制层对象中,添加用于处理禁用启用业务的控制层方法。首先在此方法中通过形参接收客户端提交的数据,然后调用业务层对象执行禁用,启用操作,最后封装执行结果,并在运行时将响应对象转换为JSON格式的字符串,响应到客户端。
关键代码设计与实现
第一步:在SysUserController中添加用于执行删除业务的方法。代码如下:

@RequestMapping("doValidById")
@ResponseBody
public JsonResult doValidById(Integer id,Integer valid){
		        sysUserService.validById(
				id,valid, "admin");//"admin"用户将来是登陆用户
		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 业务时序分析

在用户列表页面,点击添加按钮时加载编辑页面,其加载时序分析,如图-11所示:

图-11

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 核心业务分析

用户编辑页面呈现以后,
发起异步任务从服务端获取角色信息然后呈现在页面上,
其时序分析如图-12所示:

图-12

6.2 服务端关键业务及代码实现

6.2.1 Vo类定义

业务描述与设计实现
定义VO对象,基于此对象封装用户角色信息。
关键代码设计与实现
创建 CheckBox 类,基于此类对象封装角色id,角色名信息。关键代码如下:

package com.cy.pj.common.vo;

@Data //lombok
public class CheckBox implements Serializable{
	private static final long serialVersionUID = 2031967811425337153L;
	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.vo.CheckBox">
           select id,name
           from sys_roles
  </select>

6.2.4 Service接口及实现类

业务描述与设计实现
在角色业务层接口中添加查询角色id,name相关信息的业务方法。
关键代码设计与实现
第一步:在SysRoleDao接口中添加findObjects方法。关键代码如下:

  List<CheckBox> findObjects();

第二步:在SysRoleDao接口中添加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.findRoles());
	 }

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 核心业务分析

用户在编辑页面点击保存按钮时,获取编辑页面用户输入的基本信息异步提交到服务端,实现用户数据的持久化操作。其时序分析,如图-13所示:

图-13

7.2 服务端关键业务及代码实现

7.2.1 Entity类定义

业务描述与设计实现
负责封装用户的基本信息,然后有数据层持久化到数据库。
关键代码设计与实现
定义SysUser类,并通过相关数据封装用户基本信息,关键代码如下:

//po对象
package com.cy.pj.sys.entity;
@Data //lombok
public class SysUser implements Serializable{
	private static final long serialVersionUID = 177030063138338860L;
	private Integer id;
	private String username;
	private String password;
	private String salt;//盐值
	private String email;
	private String mobile;
	private Integer valid=1;
	private Integer deptId;
	private Date createdTime;
	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.entity.SysUser" useGeneratedKeys="true">
           <!-- 可以借助此元素获取一个数据(一般和主键有关) -->
           <selectKey order="AFTER" keyProperty="id" resultType="int"> 
           <!-- BEFORE:表示要在之后执行 -->
				select last_insert_id() 
			</selectKey>
      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),(2,2)... -->
       <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 ServiceException("保存对象不能为空");
    	if(StringUtils.isEmpty(entity.getUsername()))
    		throw new ServiceException("用户名不能为空");
    	if(StringUtils.isEmpty(entity.getPassword()))
    		throw new ServiceException("密码不能为空");
    	if(roleIds==null || roleIds.length==0)
    		throw new ServiceException("至少要为用户分配角色");
    	//2.保存用户自身信息
        //2.1对密码进行加密
		/* spring框架也有: DigestUtils.md5DigestAsHex((password+salt).getBytes()) ; // 但这里没有指定加密次数 因此,用下面第三方框架 shiro */
		
    	String source=entity.getPassword();
    	String salt=UUID.randomUUID().toString();
    	//1. 不可逆
    	//2. 相同字段加密相同,不同字段加密(超小概率重复)
    	SimpleHash sh=new SimpleHash(// Shiro框架
    			"MD5",// algorithmName 算法
    			 source,// 原密码
    			 salt, // 盐值
    			 1);//hashIterations 表示加密次数
    	entity.setSalt(salt);
    	entity.setPassword(sh.toHex()); //将密码加密结果转换为16进制并存储到entity内
    	//保存用户自身数据
    	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")
	@ResponseBody
	public JsonResult doSaveObject(
			SysUser entity,
			Integer[] roleIds){
		sysUserService.saveObject(entity,roleIds);
		return new JsonResult("save ok");
	}

7.1 客户端关键业务及代码实现

7.1.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.1.2 页面cancel按钮事件处理

业务描述与设计实现
点击页面cancel按钮时,加载菜单那列表页面。
关键代码设计与实现
第一步:事件注册(页面加载完成以后)

  $(".box-footer")
	  .on("click",".btn-cancel",doCancel)

第二步:事件处理函数定义

function doCancel(){
	$("#mainContentId").removeData("rowData");
	$("#mainContentId").load("user/user_list");
 }

7.1.1 页面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查询用户以及用户对应的部门和角色信息,并将其对应的信息呈现在用户编辑页面上,其时序图分析如图-14所示:

图-14

8.2 服务端关键业务及代码实现

8.2.1 DAO接口定义

业务描述与设计实现
负责基于id执行角色数据的查询操作。
关键代码设计与实现
在SysUserDao接口中定义基于用户id查询用户相关信息的方法,关键代码如下:

/** 基于用户id查询用户 */
SysUserDeptVo findObjectById(Integer id);

在SysUserRoleDao接口中定义基于用户id查询角色id信息的方法,关键代码如下:

List<Integer> findRoleIdsByUserId(Integer id);

8.2.2 Mapper文件定义

业务描述与设计实现
基于 SysUserDaoSysUserRoleDao 中方法的定义,在映射文件中添加对应的用户查询元素。
关键代码设计与实现
第一步:在 SysUserMapper.xml 中添加id为 findObjectByIdselect 元素,关键代码如下:

      <select id="findObjectById" parameterType="int" resultMap="sysUserDeptVo">
           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 ServiceException("参数数据不合法,userId="+userId);
		
		//2.业务查询
		SysUserDeptVo 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 核心业务分析

在用户编辑页面点击更新按钮时,异步提交数据到服务端,服务端要更新用户自身信息以及用户和角色关系数据,其时序图分析,如图-15所示:

图-15

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.entity.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("必须为其指定角色");
		//其它验证自己实现,例如用户名已经存在,密码长度,...
		//2.更新用户自身信息
		int rows=sysUserDao.updateObject(entity);
		//3.保存用户与角色关系数据
		sysUserRoleDao.deleteObjectsByUserId(entity.getId());
		sysUserRoleDao.insertObjects(entity.getId(),
				roleIds);
		//4.返回结果
		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);
		 }
	 })
 }

文章目录

fix - 9: 相同校验

mapper.xml

	<select id="isExist" resultType="int">
		select count(*) from sys_users
		where  ${columnName} = #{columnValue}
	</select>
	

dao

int isExist(
			@Param("columnName") String columnName ,
			@Param("columnValue") String columnValue ) ;

service

public class SysUserServiceImpl implements SysUserService {
	public void isExist(String columnName , String columnValue) {
		int rows = sysUserDao.isExist(columnName, columnValue);
		Assert.isServiceValid(rows!=0, "已存在!");
	}
}

工具类

public abstract class Assert {
	/** 业务验证 */
	public static void isServiceValid(boolean statement, String message) {
		if(statement) {
			throw new ServiceException(message) ; 
		}
	}

controller

	@RequestMapping("/doIsExist")
	public void doIsExist(String columnName , String columnValue) {
		sysUserService.isExist(columnName , columnValue);
	}

html

 $(document).ready(function(){
	 //页面加载完成以后加载角色信息并初始化页面
	 $(".form-horizontal")
		 .on("change","#usernameId,#emailId,#phoneId",isExist);
 });
 
	function isExist() {
		var url = "/user/doIsExist";
		var columnName = $(this).prop("name") ; 
		var columnValue = $(this).val() ; 
		var name = $(this).parents(".form-group").find("label").html() ; 
		var params = {
				"columnName":columnName , 
				"columnValue":columnValue
			}
		$.getJSON(url,params, function(result) {
			doHandleExist(name , result);
		});
	}
	function doHandleExist(name , result) {
		if(result.state==0){//存在 exist
			alert("\""+name+"\""+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")
@ResponseBody
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:$("#newPwdId").val(),
	              cfgPwd:$("#cfgPwdId").val()
	   }
	   //2.定义请求的url
	   var url="user/doUpdatePassword"
	   //3.异步提交请求,执行更新操作
	   $.post(url,params,function(result){
			alert(result.message);
	   })
   }


文章目录


12 总结

12.1 重难点分析

一对多数据的保存?
多对一数据的查询映射?
用户密码的加密实现?

12.2 FAQ分析

用户与角色之间是什么关系?
描述一下用户查询业务的实现?
描述一下用户添加业务的实现?
描述一下用户更新业务的实现?

12.3 BUG分析

对课上,课后Bug进行截图分析。