本模块主要是实现对用户行为日志(例如谁在什么时间点执行了什么操作,访问了哪些方法,传递的什么参数,执行时长等)进行记录、查询、删除等操作。其表设计语句如下:
CREATE TABLE `sys_logs` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL COMMENT '用户名',
`operation` varchar(50) DEFAULT NULL COMMENT '用户操作',
`method` varchar(200) DEFAULT NULL COMMENT '请求方法',
`params` varchar(5000) DEFAULT NULL COMMENT '请求参数',
`time` bigint(20) NOT NULL COMMENT '执行时长(毫秒)',
`ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
`createdTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='系统日志';
基于用户需求,实现静态页面(html/css/js),通过静态页面为用户呈现基本需求实现,如图-1所示。
图-1
说明:假如客户对此原型进行了确认,后续则可以基于此原型进行研发。
日志业务后台API分层架构及调用关系如图-2所示:
说明:分层目的主要将复杂问题简单化,实现各司其职,各尽所能。
当点击首页左侧的"日志管理"菜单时,其总体时序分析如图-3所示:
基于日志菜单管理的请求业务,在PageController中添加doLogUI方法,doPageUI方法分别用于返回日志列表页面,日志分页页面。
第一步:在PageController中定义返回日志列表的方法。代码如下:
@RequestMapping("log/log_list")
public String doLogUI() {
return "sys/log_list";
}
第二步:在PageController中定义用于返回分页页面的方法。代码如下:
@RequestMapping("doPageUI")
public String doPageUI() {
return "common/page";
}
首先准备日志列表页面(/templates/pages/sys/log_list.html),然后在starter.html页面中点击日志管理菜单时异步加载日志列表页面。
找到项目中的starter.html 页面,页面加载完成以后,注册日志管理菜单项的点击事件,当点击菜单管理时,执行事件处理函数。关键代码如下:
$(function(){
doLoadUI("load-log-id","log/log_list")
})
function doLoadUI(id,url){
$("#"+id).click(function(){
$("#mainContentId").load(url);
});
}
其中,load函数为jquery中的ajax异步请求函数。
当日志列表页面加载完成以后异步加载分页页面(page.html)。
在log_list.html页面中异步加载page页面,这样可以实现分页页面重用,哪里需要分页页面,哪里就进行页面加载即可。关键代码如下:
$(function(){
$("#pageId").load("doPageUI");
});
说明:数据加载通常是一个相对比较耗时操作,为了改善用户体验,可以先为用户呈现一个页面,数据加载时,显示数据正在加载中,数据加载完成以后再呈现数据。这样也可满足现阶段不同类型客户端需求(例如手机端,电脑端,电视端,手表端。)
日志查询服务端数据基本架构,如图-4所示。
服务端日志分页查询代码基本架构,如图-5所示:
服务端日志列表数据查询时序图,如图-6所示:
构建实体对象(POJO)封装从数据库查询到的记录,一行记录映射为内存中一个的这样的对象。对象属性定义时尽量与表中字段有一定的映射关系,并添加对应的set/get/toString等方法,便于对数据进行更好的操作。
基于Dao接口创建映射文件,在此文件中通过相关元素(例如select)描述要执行的数据操作。
第一步:在映射文件的设计目录(mapper/sys)中添加SysLogMapper.xml映射文件,代码如下:
package com.cy.pj.sys.entity;
import java.io.Serializable;
import java.util.Date;
public class SysLog implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
//用户名
private String username;
//用户操作
private String operation;
//请求方法
private String method;
//请求参数
private String params;
//执行时长(毫秒)
private Long time;
//IP地址
private String ip;
//创建时间
private Date createdTime;
/**设置:*/
public void setId(Integer id) {
this.id = id;
}
/**获取:*/
public Integer getId() {
return id;
}
/**设置:用户名*/
public void setUsername(String username) {
this.username = username;
}
/** 获取:用户名*/
public String getUsername() {
return username;
}
/**设置:用户操作*/
public void setOperation(String operation) {
this.operation = operation;
}
/**获取:用户操作*/
public String getOperation() {
return operation;
}
/**设置:请求方法*/
public void setMethod(String method) {
this.method = method;
}
/**获取:请求方法*/
public String getMethod() {
return method;
}
/** 设置:请求参数*/
public void setParams(String params) {
this.params = params;
}
/** 获取:请求参数 */
public String getParams() {
return params;
}
/**设置:IP地址 */
public void setIp(String ip) {
this.ip = ip;
}
/** 获取:IP地址*/
public String getIp() {
return ip;
}
/** 设置:创建时间*/
public void setCreateDate(Date createdTime) {
this.createdTime = createdTime;
}
/** 获取:创建时间*/
public Date getCreatedTime() {
return createdTime;
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
}
说明:通过此对象除了可以封装从数据库查询的数据,还可以封装客户端请求数据,实现层与层之间数据的传递。
思考:这个对象的set方法,get方法可能会在什么场景用到?
通过数据层对象,基于业务层参数数据查询日志记录总数以及当前页要呈现的用户行为日志信息。
第一步:定义数据层接口对象,通过将此对象保证给业务层以提供日志数据访问。代码如下:
@Mapper
public interface SysLogDao {
}
第二步:在SysLogDao接口中添加getRowCount方法用于按条件统计记录总数。代码如下:
/**
* @param username 查询条件(例如查询哪个用户的日志信息)
* @return 总记录数(基于这个结果可以计算总页数)
*/
int getRowCount(@Param("username") String username);
}
第三步:在SysLogDao接口中添加findPageObjects方法,基于此方法实现当前页记录的数据查询操作。代码如下:
* @param username 查询条件(例如查询哪个用户的日志信息)
* @param startIndex 当前页的起始位置
* @param pageSize 当前页的页面大小
* @return 当前页的日志记录信息
* 数据库中每条日志信息封装到一个SysLog对象中
*/
List
@Param("username")String username,
@Param("startIndex")Integer startIndex,
@Param("pageSize")Integer pageSize);
说明:
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
第二步:在映射文件中添加sql元素实现,SQL中的共性操作,代码如下:
from sys_Logs
username like concat("%",#{username},"%")
第三步:在映射文件中添加id为getRowCount元素,按条件统计记录总数,
代码如下:
第四步:在映射文件中添加id为findPageObjects元素,实现分页查询。代码如下:
思考:
第五步:单元测试类SysLogDaoTests,对数据层方法进行测试。
package com.cy.pj.sys.dao;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.cy.pj.sys.entity.SysLog;
@SpringBootTest
public class SysLogDaoTests {
@Autowired
private SysLogDao sysLogDao;
@Test
public void testGetRowCount() {
int rows=sysLogDao.getRowCount("admin");
System.out.println("rows="+rows);
}
@Test
public void testFindPageObjects() {
List
sysLogDao.findPageObjects("admin", 0, 3);
for(SysLog log:list) {
System.out.println(log);
}
}
}
业务层主要是实现模块中业务逻辑的处理。在日志分页查询中,业务层对象首先要通过业务方法中的参数接收控制层数据(例如username,pageCurrent)并校验。然后基于用户名进行总记录数的查询并校验,再基于起始位置及页面大小进行当前页记录的查询,最后对查询结果进行封装并返回。
业务值对象定义,基于此对象封装数据层返回的数据以及计算的分页信息,具体代码参考如下:
package com.cy.pj.common.vo;
public class PageObject
private static final long serialVersionUID = 6780580291247550747L;//类泛型
/**当前页的页码值*/
private Integer pageCurrent=1;
/**页面大小*/
private Integer pageSize=3;
/**总行数(通过查询获得)*/
private Integer rowCount=0;
/**总页数(通过计算获得)*/
private Integer pageCount=0;
/**当前页记录*/
private List
public PageObject(){}
public PageObject(Integer pageCurrent, Integer pageSize, Integer rowCount, List
super();
this.pageCurrent = pageCurrent;
this.pageSize = pageSize;
this.rowCount = rowCount;
this.records = records;
// this.pageCount=rowCount/pageSize;
// if(rowCount%pageSize!=0) {
// pageCount++;
// }
this.pageCount=(rowCount-1)/pageSize+1;
}
public Integer getPageCurrent() {
return pageCurrent;
}
public void setPageCurrent(Integer pageCurrent) {
this.pageCurrent = pageCurrent;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Integer getRowCount() {
return rowCount;
}
public void setRowCount(Integer rowCount) {
this.rowCount = rowCount;
}
public Integer getPageCount() {
return pageCount;
}
public void setPageCount(Integer pageCount) {
this.pageCount = pageCount;
}
public List
return records;
}
public void setRecords(List
this.records = records;
}
}
定义日志业务接口及方法,暴露外界对日志业务数据的访问,其代码参考如下:
package com.cy.pj.sys.service;
public interface SysLogService {
/**
* @param name 基于条件查询时的参数名
* @param pageCurrent 当前的页码值
* @return 当前页记录+分页信息
*/
PageObject
String username,
Integer pageCurrent);
}
日志业务接口及实现类,用于具体执行日志业务数据的分页查询操作,其代码如下:
package com.cy.pj.sys.service.impl;
@Service
public class SysLogServiceImpl implements SysLogService{
@Autowired
private SysLogDao sysLogDao;
@Override
public PageObject
String name, Integer pageCurrent) {
//1.验证参数合法性
//1.1验证pageCurrent的合法性,
//不合法抛出IllegalArgumentException异常
if(pageCurrent==null||pageCurrent<1)
throw new IllegalArgumentException("当前页码不正确");
//2.基于条件查询总记录数
//2.1) 执行查询
int rowCount=sysLogDao.getRowCount(name);
//2.2) 验证查询结果,假如结果为0不再执行如下操作
if(rowCount==0)
throw new ServiceException("系统没有查到对应记录");
//3.基于条件查询当前页记录(pageSize定义为2)
//3.1)定义pageSize
int pageSize=2;
//3.2)计算startIndex
int startIndex=(pageCurrent-1)*pageSize;
//3.3)执行当前数据的查询操作
List
sysLogDao.findPageObjects(name, startIndex, pageSize);
//4.对分页信息以及当前页记录进行封装
//4.1)构建PageObject对象
PageObject
//4.2)封装数据
pageObject.setPageCurrent(pageCurrent);
pageObject.setPageSize(pageSize);
pageObject.setRowCount(rowCount);
pageObject.setRecords(records);
pageObject.setPageCount((rowCount-1)/pageSize+1);
//5.返回封装结果。
return pageObject;
}
}
在当前方法中需要的ServiceException是一个自己定义的异常, 通过自定义异常可更好的实现对业务问题的描述,同时可以更好的提高用户体验。参考代码如下:
package com.cy.pj.common.exception;
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 7793296502722655579L;
public ServiceException() {
super();
}
public ServiceException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public ServiceException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
说明:几乎在所有的框架中都提供了自定义异常,例如MyBatis中的BindingException等。
定义Service对象的单元测试类,代码如下:
package com.cy.pj.sys.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.cy.pj.common.vo.PageObject;
import com.cy.pj.sys.entity.SysLog;
@SpringBootTest
public class SysLogServiceTests {
@Autowired
private SysLogService sysLogService;
@Test
public void testFindPageObjects() {
PageObject
sysLogService.findPageObjects("admin", 1);
System.out.println(pageObject);
}
}
控制层对象主要负责请求和响应数据的处理,例如,本模块首先要通过控制层对象处理请求参数,然后通过业务层对象执行业务逻辑,再通过VO对象封装响应结果(主要对业务层数据添加状态信息),最后将响应结果转换为JSON格式的字符串响应到客户端。
定义控制层值对象(VO),目的是基于此对象封装控制层响应结果(在此对象中主要是为业务层执行结果添加状态信息)。Spring MVC框架在响应时可以调用相关API(例如jackson)将其对象转换为JSON格式字符串。
package com.cy.pj.common.vo;
public class JsonResult implements Serializable {
private static final long serialVersionUID = -856924038217431339L;//SysResult/Result/R
/**状态码*/
private int state=1;//1表示SUCCESS,0表示ERROR
/**状态信息*/
private String message="ok";
/**正确数据*/
private Object data;
public JsonResult() {}
public JsonResult(String message){
this.message=message;
}
/**一般查询时调用,封装查询结果*/
public JsonResult(Object data) {
this.data=data;
}
/**出现异常时时调用*/
public JsonResult(Throwable t){
this.state=0;
this.message=t.getMessage();
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
定义Controller类,并将此类对象使用Spring框架中的@Controller注解进行标识,表示此类对象要交给Spring管理。然后基于@RequestMapping注解为此类定义根路径映射。代码参考如下:
package com.cy.pj.sys.controller;
@Controller
@RequestMapping("/log/")
public class SysLogController {
@Autowired
private SysLogService sysLogService;
}
@RequestMapping("doFindPageObjects")
@ResponseBody
public JsonResult doFindPageObjects(String username,Integer pageCurrent){
PageObject
sysLogService.findPageObjects(username,pageCurrent);
return new JsonResult(pageObject);
}
定义全局异常处理类,对控制层可能出现的异常,进行统一异常处理,代码如下:
package com.cy.pj.common.web;
import java.util.logging.Logger;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.cy.pj.common.vo.JsonResult;
@ControllerAdvice
public class GlobalExceptionHandler {
//JDK中的自带的日志API
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JsonResult doHandleRuntimeException(
RuntimeException e){
e.printStackTrace();//也可以写日志
异常信息
}return new JsonResult(e);//封装
}
控制层响应数据处理分析,如图-7所示:
当用户点击首页日志管理时,其页面流转分析如图-8所示:
日志分页页面加载完成以后,向服务端发起异步请求加载日志信息,当日志信息加载完成需要将日志信息、分页信息呈现到列表页面上。
第一步:分页页面加载完成,向服务端发起异步请求,代码参考如下:
$(function(){
//为什么要将doGetObjects函数写到load函数对应的回调内部。
$("#pageId").load("doPageUI",function(){
doGetObjects();
});
}
第二步:定义异步请求处理函数,代码参考如下:
function doGetObjects(){
//debugger;//断点调试
//1.定义url和参数
var url="log/doFindPageObjects"
var params={"pageCurrent":1};//pageCurrent=2
//2.发起异步请求
//请问如下ajax请求的回调函数参数名可以是任意吗?//可以,必须符合标识符的规范
$.getJSON(url,params,function(result){
//请问result是一个字符串还是json格式的js对象?对象
doHandleQueryResponseResult(result);
}
);//特殊的ajax函数
}
result 结果对象分析,如图-9所示:
第三步:定义回调函数,处理服务端的响应结果。代码如下:
function doHandleQueryResponseResult (result){ //JsonResult
if(result.state==1){//ok
//更新table中tbody内部的数据
doSetTableBodyRows(result.data.records);//将数据呈现在页面上
//更新页面page.html分页数据
//doSetPagination(result.data); //此方法写到page.html中
}else{
alert(result.message);
}
}
第四步:将异步响应结果呈现在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=$("
//2.2 构建tds对象
var tds=doCreateTds(records[i]);
//2.3 将tds追加到tr中
tr.append(tds);
//2.4 将tr追加到tbody中
tBody.append(tr);
}
}
第五步:创建每行中的td元素,并填充具体业务数据。代码参考如下:
var tds="
"
"
"
"
"
"
return tds;
}
日志信息列表初始化完成以后初始化分页数据(调用setPagination函数),然后再点击上一页,下一页等操作时,更新页码值,执行基于当前页码值的查询。
第一步:在page.html页面中定义doSetPagination方法(实现分页数据初始化),代码如下:
function doSetPagination(page){
//1.始化数据
$(".rowCount").html("总记录数("+page.rowCount+")");
$(".pageCount").html("总页数("+page.pageCount+")");
$(".pageCurrent").html("当前页("+page.pageCurrent+")");
//2.绑定数据(为后续对此数据的使用提供服务)
$("#pageId").data("pageCurrent",page.pageCurrent);
$("#pageId").data("pageCount",page.pageCount);
}
第二步:分页页面page.html中注册点击事件。代码如下:
$(function(){
//事件注册
$("#pageId").on("click",".first,.pre,.next,.last",doJumpToPage);
})
第三步:定义doJumpToPage方法(通过此方法实现当前数据查询)
function doJumpToPage(){
//1.获取点击对象的class值
var cls=$(this).prop("class");//Property
//2.基于点击的对象执行pageCurrent值的修改
//2.1获取pageCurrent,pageCount的当前值
var pageCurrent=$("#pageId").data("pageCurrent");
var pageCount=$("#pageId").data("pageCount");
//2.2修改pageCurrent的值
if(cls=="first"){//首页
pageCurrent=1;
}else if(cls=="pre"&&pageCurrent>1){//上一页
pageCurrent--;
}else if(cls=="next"&&pageCurrent pageCurrent++; }else if(cls=="last"){//最后一页 pageCurrent=pageCount; }else{ return; } //3.对pageCurrent值进行重新绑定 $("#pageId").data("pageCurrent",pageCurrent); //4.基于新的pageCurrent的值进行当前页数据查询 doGetObjects(); } 修改分页查询方法:(看黄色底色部分) function doGetObjects(){ //debugger;//断点调试 //1.定义url和参数 var url="log/doFindPageObjects" //? 请问data函数的含义是什么?(从指定元素上获取绑定的数据) //此数据会在何时进行绑定?(setPagination,doQueryObjects) var pageCurrent=$("#pageId").data("pageCurrent"); //为什么要执行如下语句的判定,然后初始化pageCurrent的值为1 //pageCurrent参数在没有赋值的情况下,默认初始值应该为1. if(!pageCurrent) pageCurrent=1; var params={"pageCurrent":pageCurrent};//pageCurrent=2 //2.发起异步请求 //请问如下ajax请求的回调函数参数名可以是任意吗?可以,必须符合标识符的规范 $.getJSON(url,params,function(result){ //请问result是一个字符串还是json格式的js对象?对象 doH andleResponseResult(result); } );//特殊的ajax函数 } 当用户点击日志列表的查询按钮时,基于用户输入的用户名进行有条件的分页查询,并将查询结果呈现在页面。 第一步:日志列表页面加载完成,在查询按钮上进行事件注册。代码如下: $(".input-group-btn").on("click",".btn-search",doQueryObjects) function doQueryObjects(){ //为什么要在此位置初始化pageCurrent的值为1? //数据查询时页码的初始位置也应该是第一页 $("#pageId").data("pageCurrent",1); //为什么要调用doGetObjects函数? //重用js代码,简化jS代码编写。 doGetObjects(); } 第三步:在分页查询函数中追加name参数定义(看黄色底色部分),代码如下: function doGetObjects(){ //debugger;//断点调试 //1.定义url和参数 var url="log/doFindPageObjects" //? 请问data函数的含义是什么?(从指定元素上获取绑定的数据) //此数据会在何时进行绑定?(setPagination,doQueryObjects) var pageCurrent=$("#pageId").data("pageCurrent"); //为什么要执行如下语句的判定,然后初始化pageCurrent的值为1 //pageCurrent参数在没有赋值的情况下,默认初始值应该为1. if(!pageCurrent) pageCurrent=1; var params={"pageCurrent":pageCurrent}; //为什么此位置要获取查询参数的值? //一种冗余的应用方法,目的时让此函数在查询时可以重用。 var username=$("#searchNameId").val(); //如下语句的含义是什么?动态在json格式的js对象中添加key/value, if(username) params.username=username;//查询时需要 //2.发起异步请求 //请问如下ajax请求的回调函数参数名可以是任意吗?可以,必须符合标识符的规范 $.getJSON(url,params,function(result){ //请问result是一个字符串还是json格式的js对象?对象 doHandleResponseResult(result); } ); } 当用户执行日志删除操作时,客户端与务端交互时的基本数据架构,如图-10所示。 客户端提交删除请求,服务端对象的工作时序分析,如图-11所示。 数据层基于业务层提交的日志记录id,进行日志删除操作。 在SysLogDao中添加基于id执行日志删除的方法。代码参考如下: int deleteObjects(@Param("ids")Integer… ids); 在SysLogDao接口对应的映射文件中添加用于执行删除业务的delete元素,此元素内部定义具体的SQL实现。 在SysLogMapper.xml文件添加delete元素,关键代码如下: delete from sys_Logs where id in open="(" close=")" separator="," item="id"> #{id} FAQ分析:如上SQL实现可能会存在什么问题?(可靠性问题,性能问题) 从可靠性的角度分析,假如ids的值为null或长度为0时,SQL构建可能会出现语法问题,可参考如下代码进行改进(先对ids的值进行判定): delete from sys_logs where id in open="(" close=")" separator="," item="id"> #{id} where 1==2 从SQL执行性能角度分析,一般在SQL语句中不建议使用in表达式,可以参考如下代码进行实现(重点是forearch中or运算符的应用): delete from sys_logs item="id" separator="or"> id=#{id} where 1==2 说明:这里的choose元素也为一种选择结构,when元素相当于if,otherwise相当于else的语法。 在日志业务层定义用于执行删除业务的方法,首先通过方法参数接收控制层传递的多个记录的id,并对参数id进行校验。然后基于日志记录id执行删除业务实现。最后返回业务执行结果。 第一步:在SysLogService接口中,添加基于多个id进行日志删除的方法。关键代码如下: int deleteObjects(Integer… ids) {} 第二步:在SysLogServiceImpl实现类中添加删除业务的具体实现。关键代码如下: @Override public int deleteObjects(Integer… ids) { //1.判定参数合法性 if(ids==null||ids.length==0) throw new IllegalArgumentException("请选择一个"); //2.执行删除操作 int rows; try{ rows=sysLogDao.deleteObjects(ids); }catch(Throwable e){ e.printStackTrace(); //发出报警信息(例如给运维人员发短信) throw new ServiceException("系统故障,正在恢复中..."); } //4.对结果进行验证 if(rows==0) throw new ServiceException("记录可能已经不存在"); //5.返回结果 return rows; } 在日志控制层对象中,添加用于处理日志删除请求的方法。首先在此方法中通过形参接收客户端提交的数据,然后调用业务层对象执行删除操作,最后封装执行结果,并在运行时将响应对象转换为JSON格式的字符串,响应到客户端。 第一步:在SysLogController中添加用于执行删除业务的方法。代码如下: @RequestMapping("doDeleteObjects") @ResponseBody public JsonResult doDeleteObjects(Integer… ids){ sysLogService.deleteObjects(ids); return new JsonResult("delete ok"); } 第二步:启动tomcat进行访问测试,打开浏览器输入如下网址: http://localhost/log/doDeleteObjects?ids=1,2,3 用户在页面上首先选择要删除的元素,然后点击删除按钮,将用户选择的记录id异步提交到服务端,最后在服务端执行日志的删除动作。 第一步:页面加载完成以后,在删除按钮上进行点击事件注册。关键代码如下: ... $(".input-group-btn") .on("click",".btn-delete",doDeleteObjects) ... 第二步:定义删除操作对应的事件处理函数。关键代码如下: function doDeleteObjects(){ //1.获取选中的id值 var ids=doGetCheckedIds(); if(ids.length==0){ alert("至少选择一个"); return; } //2.发异步请求执行删除操作 var url="log/doDeleteObjects"; var params={"ids":ids.toString()}; console.log(params); $.post(url,params,function(result){ if(result.state==1){ alert(result.message); doGetObjects(); }else{ alert(result.message); } }); } 第三步:定义获取用户选中的记录id的函数。关键代码如下: function doGetCheckedIds(){ //定义一个数组,用于存储选中的checkbox的id值 var array=[];//new Array(); //获取tbody中所有类型为checkbox的input元素 $("#tbodyId input[type=checkbox]"). //迭代这些元素,每发现一个元素都会执行如下回调函数 each(function(){ //假如此元素的checked属性的值为true if($(this).prop("checked")){ //调用数组对象的push方法将选中对象的值存储到数组 array.push($(this).val()); } }); return array; } 第四步:Thead中全选元素的状态影响tbody中checkbox对象状态。代码如下: function doChangeTBodyCheckBoxState(){ //1.获取当前点击对象的checked属性的值 var flag=$(this).prop("checked");//true or false //2.将tbody中所有checkbox元素的值都修改为flag对应的值。 //第一种方案 /* $("#tbodyId input[name='cItem']") .each(function(){ $(this).prop("checked",flag); }); */ //第二种方案 $("#tbodyId input[type='checkbox']") .prop("checked",flag); } 第五步:Tbody中checkbox的状态影响thead中全选元素的状态。代码如下: function doChangeTHeadCheckBoxState(){ //1.设定默认状态值 var flag=true; //2.迭代所有tbody中的checkbox值并进行与操作 $("#tbodyId input[type='checkbox']") .each(function(){ flag=flag&$(this).prop("checked") }); //3.修改全选元素checkbox的值为flag $("#checkAll").prop("checked",flag); } 第六步:完善业务刷新方法,当在最后一页执行删除操作时,基于全选按钮状态及当前页码值,刷新页面。关键代码如下: function doRefreshAfterDeleteOK(){ var pageCount=$("#pageId").data("pageCount"); var pageCurrent=$("#pageId").data("pageCurrent"); var checked=$("#checkAll").prop("checked"); if(pageCurrent==pageCount&&checked&&pageCurrent>1){ pageCurrent--; $("#pageId").data("pageCurrent",pageCurrent); } doGetObjects(); } 说明:最后将如上方法添加在删除操作成功以后的代码块中。 数据层基于业务层的持久化请求,将业务层提交的用户行为日志信息写入到数据库。 在SysLogDao接口中添加,用于实现日志信息持久化的方法。关键代码如下: int insertObject(SysLog entity); 基于SysLogDao中方法的定义,编写用于数据持久化的SQL元素。 在SysLogMapper.xml中添加insertObject元素,用于向日志表写入用户行为日志。关键代码如下: insert into sys_logs (username,operation,method,params,time,ip,createdTime) values (#{username},#{operation},#{method},#{params},#{time},#{ip},#{createdTime}) 将日志切面中抓取到的用户行为日志信息,通过业务层对象方法持久化到数据库。 第一步:在SysLogService接口中,添加保存日志信息的方法。关键代码如下: void saveObject(SysLog entity) 第二步:在SysLogServiceImpl类中添加,保存日志的方法实现。关键代码如下: @Override public void saveObject(SysLog entity) { sysLogDao.insertObject(entity); } (MappedStatement)。 说明:当动态sql的参数在接口中没有使用@Param注解修饰,还可以借助_parameter这个变量获取参数的值(mybatis中的一种规范)。 定义日志切面类对象,通过环绕通知处理日志记录操作。关键代码如下: @Aspect @Component public class SysLogAspect { private Logger log=Logger.getLogger(SysLogAspect.class); @Autowired private SysLogService sysLogService; @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)") public void logPointCut(){} @Around("logPointCut()") public Object around(ProceedingJoinPoint //连接点 jointPoint) throws Throwable{ long startTime=System.currentTimeMillis(); //执行目标方法(result为目标方法的执行结果) Object result=jointPoint.proceed(); long endTime=System.currentTimeMillis(); long totalTime=endTime-startTime; log.info("方法执行的总时长为:"+totalTime); saveSysLog(jointPoint,totalTime); return result; } private void saveSysLog(ProceedingJoinPoint point, long totleTime) throws NoSuchMethodException, SecurityException, JsonProcessingException{ //1.获取日志信息 MethodSignature ms= (MethodSignature)point.getSignature(); Class> targetClass=point.getTarget().getClass(); String className=targetClass.getName(); //获取接口声明的方法 String methodName=ms.getMethod().getName(); Class>[] parameterTypes=ms.getMethod().getParameterTypes(); //获取目标对象方法(AOP版本不同,可能获取方法对象方式也不同) Method targetMethod=targetClass.getDeclaredMethod( methodName,parameterTypes); //获取用户名,学完shiro再进行自定义实现,没有就先给固定值 String username=ShiroUtils.getPrincipal().getUsername(); //获取方法参数 Object[] paramsObj=point.getArgs(); System.out.println("paramsObj="+paramsObj); //将参数转换为字符串 String params=new ObjectMapper() .writeValueAsString(paramsObj); //2.封装日志信息 SysLog log=new SysLog(); log.setUsername(username);//登陆的用户 //假如目标方法对象上有注解,我们获取注解定义的操作值 RequiredLog requestLog= targetMethod.getDeclaredAnnotation(RequiredLog.class); if(requestLog!=null){ log.setOperation(requestLog.value()); } log.setMethod(className+"."+methodName);//className.methodName() log.setParams(params);//method params log.setIp(IPUtils.getIpAddr());//ip 地址 log.setTime(totleTime);// log.setCreateDate(new Date()); //3.保存日志信息 sysLogService.insertObject(log); } } 方法中用到的ip地址获取需要提供一个如下的工具类:(不用自己实现,直接用) public class IPUtils { private static Logger logger = LoggerFactory.getLogger(IPUtils.class); public static String getIpAddr() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String ip = null; try { ip = request.getHeader("x-forwarded-for"); if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } catch (Exception e) { logger.error("IPUtils ERROR ", e); } return ip; } } 原理分析,如图-12所示: 问题分析: 图-14 问题分析: 图-15 问题分析: 问题分析:getRowCount元素可能没有写resultType或resultMap。 问题分析:绑定异常,检测findPageObjects方法参数与映射文件参数名字是否匹配。 问题分析:应该是查询时的结果映射对的类全名写错了。 问题分析:请求方式与控制层处理方式不匹配。 问题分析:服务端响应数据不正确,例如服务端没有注册将对象转换为JSON串的Bean 图-21 问题分析:客户端请求参数中不包含服务端控制层方法参数或格式不匹配。 图-22 问题分析:点击右侧VM176:64位置进行查看。 图-23 问题分析:找到对应位置,检测data的值以及数据来源。 图-24 问题分析:找到对应位置,假如无法确定位置,可排除法或打桩,debug分析。 问题分析:调用length方法的对象有问题,可先检测下对象的值。 问题分析:检测record定义或赋值的地方。 问题分析:服务端资源没找到,检查url和controller映射,不要点击图中的jquery。 package com.cy.pj.common.exception; public class ServiceException extends RuntimeException { /** public ServiceException() { public ServiceException(String message) { public ServiceException(Throwable cause) { } package com.cy.pj.common.utils; import com.cy.pj.common.exception.ServiceException; public class AssertUtils { public static void isArgValid(boolean statement,String msg) { package com.cy.pj.common.web; import org.springframework.web.bind.annotation.ControllerAdvice; import com.cy.pj.common.pojo.JsonResult; /** /** package com.cy.pj.common.pojo; import java.io.Serializable; import lombok.Data; @Data /** package com.cy.pj.common.pojo; import java.io.Serializable; import lombok.Data; /** package com.cy.pj.common.pojo; import java.io.Serializable; import lombok.Data; @Data /** package com.cy.pj.common.pojo; import java.io.Serializable; import lombok.Data; public PageObject(Long rowCount, List public PageObject(Long rowCount, List package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; @Data /** } package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; @Data /** } package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; /** /** package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; /** private static final long serialVersionUID = -2671028987524519218L; package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; @Data /** } package com.cy.pj.sys.pojo; import java.io.Serializable; import lombok.Data; @Data /** package com.cy.pj.sys.dao; import org.apache.ibatis.annotations.Delete; import com.cy.pj.common.pojo.Node; @Mapper package com.cy.pj.sys.dao; import java.util.List; import org.apache.ibatis.annotations.Mapper; import com.cy.pj.sys.pojo.SysLog; @Mapper /** package com.cy.pj.sys.dao; import java.util.List; import org.apache.ibatis.annotations.Delete; import com.cy.pj.common.pojo.Node; @Mapper /** package com.cy.pj.sys.dao; import java.util.List; import org.apache.ibatis.annotations.Insert; import com.cy.pj.common.pojo.CheckBox; @Mapper /** /** package com.cy.pj.sys.dao; import java.util.List; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Mapper; package com.cy.pj.sys.dao; import java.util.List; import org.apache.ibatis.annotations.Mapper; import com.cy.pj.sys.pojo.SysUser; @Mapper /** package com.cy.pj.sys.dao; import java.util.List; import org.apache.ibatis.annotations.Delete; /** @Select("select role_id from sys_user_roles where user_id=#{userId}") package com.cy.pj.sys.service; import java.util.List; import com.cy.pj.common.pojo.Node; public interface SysDeptService { package com.cy.pj.sys.service; import com.cy.pj.common.pojo.PageObject; /** package com.cy.pj.sys.service; import java.util.List; import com.cy.pj.common.pojo.Node; public interface SysMenuService { List package com.cy.pj.sys.service; import java.util.List; import com.cy.pj.common.pojo.CheckBox; public interface SysRoleService { /** package com.cy.pj.sys.service; import java.util.Map; import com.cy.pj.common.pojo.PageObject; public interface SysUserService { PageObject package com.cy.pj.sys.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.exception.ServiceException; @Service package com.cy.pj.sys.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.exception.ServiceException; @Service @Override } package com.cy.pj.sys.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.exception.ServiceException; @Service @Autowired public List package com.cy.pj.sys.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.exception.ServiceException; @Service @Autowired } package com.cy.pj.sys.service.impl; import java.util.HashMap; import org.apache.shiro.crypto.hash.SimpleHash; import com.cy.pj.common.exception.ServiceException; @Service @Autowired } package com.cy.pj.sys.controller; import org.springframework.stereotype.Controller; package com.cy.pj.sys.controller; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.pojo.JsonResult; @RestController package com.cy.pj.sys.controller; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.pojo.JsonResult; @Controller @Autowired package com.cy.pj.sys.controller; import org.springframework.beans.factory.annotation.Autowired; //@Controller @Autowired package com.cy.pj.sys.controller; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.pojo.JsonResult; @RestController @Autowired package com.cy.pj.sys.controller; import org.springframework.beans.factory.annotation.Autowired; import com.cy.pj.common.pojo.JsonResult; @RestController @Autowired package com.cy; import org.springframework.boot.SpringApplication; @SpringBootApplication public static void main(String[] args) { }
列表页面信息查询实现
日志管理删除操作实现
数据架构分析
删除业务时序分析
服务端关键业务及代码实现
Dao接口实现
Mapper文件实现
Service接口及实现类
Controller类实现
客户端关键业务及代码实现
日志列表页面事件处理
日志管理数据添加实现
服务端关键业务及代码实现
Dao接口实现
Mapper映射文件
Service接口及实现类
日志切面Aspect实现
总结
重难点分析
FAQ分析
BUG分析
*
*/
private static final long serialVersionUID = -5598865415547474216L;
super();
// TODO Auto-generated constructor stub
}
super(message);
// TODO Auto-generated constructor stub
}
super(cause);
// TODO Auto-generated constructor stub
}
if(statement)
throw new IllegalArgumentException(msg);
}
public static void isServiceValid(boolean statement,String msg) {
if(statement)
throw new ServiceException(msg);
}
}
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
* 此注解描述的类为一个控制层全局异常处理类,在这个类可以定义异常处理方法,
* 基于这些异常处理方法对异常进行处理
* @author huawei
*
*/
@ControllerAdvice
public class GlobalExceptionHandler {
* @ExceptionHandler 此注解描述的方法为一个异常处理方法,在注解内部定义的异常类型
* 为此方法可以处理的异常类型(包括异常的子类类型)
* @param e 用于接收出现的异常
* @return
*/
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JsonResult doHandleRuntimeException(RuntimeException e) {
e.printStackTrace();//在后台控制台打印异常
return new JsonResult(e);//封装异常信息
}
}
public class CheckBox implements Serializable{
*
*/
private static final long serialVersionUID = -3930756932197466333L;
private Integer id;
private String name;
}
import lombok.NoArgsConstructor;
* 借助此对象封装控制层响应到客户端的数据,在这个对象中会为数据添加一个状态
* @author huawei
*
*/
@Data
@NoArgsConstructor
public class JsonResult implements Serializable{
/**
*
*/
private static final long serialVersionUID = 5110901796917551720L;
/**
* 状态码:信息标识
*/
private Integer state=1;//1:success,0:error
/*状态码对应的信息*/
private String message="success";
/*业务层响应给控制层的数据*/
private Object data;
public JsonResult(String message) {
this.message=message;
}
public JsonResult(Object data) {
this.data=data;
}
public JsonResult(Throwable e) {
this.state=0;
this.message=e.getMessage();//获取异常对象中的异常信息
}
}
public class Node implements Serializable{
* 基于此对象存储树节点信息
*/
private static final long serialVersionUID = -7022202313802285223L;
private Integer id;
private String name;
private Integer parentId;
}
import java.util.List;
import lombok.NoArgsConstructor;
/**
* 基于此对象封装分页信息
* @author huawei
*
*/
@Data
@NoArgsConstructor
public class PageObject
/**
*
*/
private static final long serialVersionUID = -2123021917437043740L;
/*总记录数*/
private Long rowCount;//池化思想
/*当前页记录*/
private List
/*总页数*/
private Long pageCount;
/*页面大小(每页最多显示多少条记录)*/
private Integer pageSize;
/*当前页码值*/
private Long pageCurrent;
super();
this.rowCount = rowCount;
this.records = records;
this.pageCount = pageCount;
this.pageSize = pageSize;
this.pageCurrent = pageCurrent;
}
super();
this.rowCount = rowCount;
this.records = records;
this.pageSize = pageSize;
this.pageCurrent = pageCurrent;
//计算pageCount的值,方法1
// this.pageCount=rowCount/pageSize;
// if(rowCount%pageSize!=0)this.pageCount++;
//计算pageCount的值,方法1
this.pageCount=(this.rowCount-1)/pageSize+1;
}
}
import java.util.Date;
/**
* 部门PO对象
*/
@Data
public class SysDept implements Serializable{
private static final long serialVersionUID = 8876920804134951849L;
private Integer id;
private String name;
private Integer parentId;
private Integer sort;
private String note;
private Date createdTime;
private Date modifiedTime;
private String createdUser;
private String modifiedUser;
}
import java.util.Date;
public class SysLog implements Serializable{//ObjectOutputStream/ObjectInputStream
* 基于此对象封装从数据库查询到的日志信息
* 类似对象的特征:
* 1.添加set/get/toString/constructor
* 2.实现Serializable接口(建议所有用于封装数据的对象都实现此接口)
* 2.1序列化:将对象转换为字节便于传输和存储
* 2.2反序列化:将网络中的或存储介质中的字节转换为对象
*/
private static final long serialVersionUID = -8562927111682153360L;
private Integer id;
//用户名
private String username;
//用户操作
private String operation;
//请求方法
private String method;
//请求参数
private String params;
//执行时长(毫秒)
private Long time;
//IP地址
private String ip;
//创建时间
private Date createdTime;
import java.util.Date;
public class SysMenu implements Serializable{
*
*/
private static final long serialVersionUID = -7691821439056974454L;
private Integer id;
/**菜单名称*/
private String name;
/**菜单url: log/doFindPageObjects*/
private String url;
/**菜单类型(两种:按钮,普通菜单)*/
private Integer type=1;
/**排序(序号)*/
private Integer sort;
/**备注*/
private String note;
/**上级菜单id*/
private Integer parentId;
/**菜单对应的权限标识(sys:log:delete)*/
private String permission;
/**创建用户*/
private String createdUser;
/**修改用户*/
private String modifiedUser;
private Date createdTime;
private Date modifiedTime;
import java.util.Date;
* 基于此对象封装角色信息
* @author huawei
*
*/
@Data
public class SysRole implements Serializable{
*
*/
private static final long serialVersionUID = -8557710039441592130L;
private Integer id;
private String name;
private String note;
private Date createdTime;
private Date modifiedTime;
private String createdUser;
private String modifiedUser;
}
import java.util.List;
* 当基于角色id执行角色以及对应的菜单信息查询时,将查询到结果存储到此对象,
* 并将此对象的数据最终更新到编辑页面上。
* 查询方案:(现在数据来自两张表)
* 1.业务层进行多次单表查询(先查角色表再查菜单表)--最简单的一种
* 2.数据层做嵌套查询(数据层多次查询,基于角色表的查询结果再次查询角色菜单关系表)
* 3.在数据层执行多表关联查询(join)
*
*/
@Data
public class SysRoleMenu implements Serializable{
/*角色id*/
private Integer id;
/*角色名称*/
private String name;
/*角色备注*/
private String note;
/*角色对应的菜单id(这个数据存在角色和菜单关系表中)*/
private List
}
import java.util.Date;
public class SysUser implements Serializable{
*
*/
private static final long serialVersionUID = -3859097288799746946L;
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;
import java.util.Date;
public class SysUserDept implements Serializable{
*
*/
private static final long serialVersionUID = -6219081603383521954L;
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;
}
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.cy.pj.sys.pojo.SysDept;
public interface SysDeptDao {
/**
* 查询所有部门以及部门的上级菜单信息
* @return
*/
@Select("select c.*,p.name parentName from sys_depts c left join sys_depts p on c.parentId=p.id")
List
import org.apache.ibatis.annotations.Param;
public interface SysLogDao {
* 基于id删除多条日志信息
* @param ids
* @return
*/
int deleteObjects(@Param("ids") Integer... ids);
/**
* 基于条件查询用户行为日志记录总数
* @param name 查询条件(用户名-基于此用户名进行模糊查询)
* @return 查询到的记录总数
*/
long getRowCount(@Param("username")String username);
/**
* 基于条件查询当前页数据
* @param name 查询条件
* @param startIndex 起始位置
* @param pageSize 页面大小(每页最多显示多少条记录)
* @return 查询到的记录
*/
List
String username,@Param("startIndex")Long startIndex,@Param("pageSize")Integer pageSize);
}
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.cy.pj.sys.pojo.SysMenu;
public interface SysMenuDao {
int updateObject(SysMenu entity);
int insertObject(SysMenu entity);
@Select("select id,name,parentId from sys_menus")
List
/**
* 基于id删除当前菜单对象
* @param id
* @return
*/
@Delete("delete from sys_menus where id=#{id}")
int deleteObject(Integer id);
* 基于id获取当前菜单对应的子菜单个数
* @param id 菜单id
* @return 子菜单的个数
*/
@Select("select count(*) from sys_menus where parentId=#{id}")
int getChildCount(Integer id);
/**
* 查询所有菜单信息以及这个菜单对应的上级菜单信息
* @return
*/
List
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.cy.pj.sys.pojo.SysRole;
import com.cy.pj.sys.pojo.SysRoleMenu;
public interface SysRoleDao {//sys_roles
/**
* 查询所有角色id和name
* @return
*/
@Select("select id,name from sys_roles")
List
int updateObject(SysRole entity);
* 基于角色id查找自身信息
* @param id
* @return
*/
//@Select("select id,name,note from sys_roles where id=#{id}")
SysRoleMenu findObjectById(Integer id);
/**
* 保存角色自身信息
* @param entity
* @return
*/
@Insert("insert into sys_roles (name,note,createdUser,modifiedUser,createdTime,modifiedTime) values (#{name},#{note},#{createdUser},#{modifiedUser},now(),now())")
@Options(useGeneratedKeys = true,keyProperty = "id")
int insertObject(SysRole entity);
* 基于角色名统计查询角色信息
* @param name 角色名
* @return 统计的个数
*/
long getRowCount(@Param("name")String name);
/**
* 按条件从指定位置查询当前页角色信息
* @param name 角色名
* @param startIndex 起始位置
* @param pageSize 页面大小
* @return 当前页的角色信息
*/
List
}
import org.apache.ibatis.annotations.Select;
/**
* 基于此对象操作角色和菜单关系表(sys_role_menus)数据
* @author huawei
*
*/
@Mapper
public interface SysRoleMenuDao {
/**
* 基于角色id删除角色和菜单关系数据
* @param roleId
* @return
*/
@Delete("delete from sys_role_menus where role_id=#{roleId}")
int deleteObjectsByRoleId(Integer roleId);
/**
* 基于角色id获取菜单id
* @param id
* @return
*/
@Select("select menu_id from sys_role_menus where role_id=#{id}")
List
int insertObjects(Integer roleId,Integer[] menuIds);
/**
* 基于菜单id删除角色和菜单关系数据
* @param menuId
* @return
*/
@Delete("delete from sys_role_menus where menu_id=#{menuId}")
int deleteObjectsByMenuId(Integer menuId);
}
import java.util.Map;
import org.apache.ibatis.annotations.Update;
import com.cy.pj.sys.pojo.SysUserDept;
public interface SysUserDao {
/**
* 更新用户自身信息
* @param entity
* @return
*/
int updateObject(SysUser entity);
/**
* 基于用户id查询用户和部门信息
* @param id
* @return
*/
SysUserDept findObjectById(Integer id);
/**
* 将用户自身信息写到数据库
* @param entity
* @return
*/
int insertObject(SysUser entity);
/**
* 修改用户状态
* @param id
* @param stste
* @param modifiedUser
* @return
*/
@Update("update sys_users set valid=#{valid},modifiedUser=#{modifiedUser},modifiedTime=now() where id=#{id}")
int validById(Integer id,Integer valid,String modifiedUser);
* 基于用户名进行用户信息的模糊查询,获取总记录数
* @param username
* @return
*/
long getRowCount(String username);
/**
* 基于用户信息的模糊查询,获取当前页记录,并将数据封装到pojo对象
* SysUserDept
* @param username 查询条件
* @param startIndex 起始位置
* @param pageSize 页面大小
* @return 当前页查询到的记录
*/
List
}
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
* 基于此dao操作用户角色关系表数据
* @author huawei
*
*/
@Mapper
public interface SysUserRoleDao {
@Delete("delete from sys_user_roles where user_id=#{userId}")
int deleteObjectsByUserId(Integer userId);
List
int insertObjects(Integer userId,Integer[]roleIds);
}
import java.util.Map;
import com.cy.pj.sys.pojo.SysDept;
List
import com.cy.pj.sys.pojo.SysLog;
/**
* 现在的项目都会分层设计,每次对象都会对应的接口,
* 层与层对象进行耦合时,建议耦合与接口
* @author huawei
*
*/
public interface SysLogService {
int deleteObjects(Integer... ids);
* 分页查询日志信息
* @param username 用户名
* @param pageCurrent 当前页页码
* @return 封装了查询和计算结果的一个分页对象
*/
PageObject
}
import java.util.Map;
import com.cy.pj.sys.pojo.SysMenu;
/**
* 更新菜单信息
* @param entity
* @return
*/
int updateObject(SysMenu entity);
/**
* 保存菜单信息
* @param entity
* @return
*/
int saveObject(SysMenu entity);
/**
*
* @return
*/
List
/**
* 基于菜单id删除菜单以及菜单对应的关系数据
* @param id
* @return 删除的行数
*/
int deleteObject(Integer id);
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.pojo.SysRole;
import com.cy.pj.sys.pojo.SysRoleMenu;
List
/**
* 基于角色id获取角色以及角色对应的菜单id
* @param id
* @return
*/
SysRoleMenu findObjectById(Integer id);
int updateObject(SysRole entity,Integer[] menuIds);
int saveObject(SysRole entity,Integer[] menuIds);
* 查询当前页角色信息
* @param name
* @param pageCurrent
* @return
*/
PageObject
}
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.pojo.SysUserDept;
int updateObject(SysUser entity,Integer[]roleIds);
Map
int saveObject(SysUser entity,Integer[]roleIds);
int validById(Integer id,Integer valid);
}
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.cy.pj.common.pojo.Node;
import com.cy.pj.sys.dao.SysDeptDao;
import com.cy.pj.sys.pojo.SysDept;
import com.cy.pj.sys.service.SysDeptService;
public class SysDeptServiceImpl implements SysDeptService {
@Autowired
private SysDeptDao sysDeptDao;
@Override
public List
}
import org.springframework.stereotype.Service;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.dao.SysLogDao;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
public class SysLogServiceImpl implements SysLogService {
@Autowired
private SysLogDao sysLogDao;
@Override
public int deleteObjects(Integer... ids) {
if(ids==null||ids.length==0)
throw new IllegalArgumentException("请输入id的值");
int rows = sysLogDao.deleteObjects(ids);
if(rows==0)
throw new ServiceException("记录可能已经不存在");
return rows;
}
public PageObject
//验证参数的有效性
if(pageCurrent==null||pageCurrent<1)
throw new IllegalArgumentException("当前页码值无效");
//统计总记录数
long rowCount = sysLogDao.getRowCount(username);
if(rowCount==0)
throw new ServiceException("没有找到对应的结果");
//查询当前数据
int pageSize=5;//这个值也可以从页面传递到服务端
long startIndex=(pageCurrent-1)*pageSize;
List
//封装查询结果以及计算结果
// PageObject
// po.setRowCount(rowCount);
// po.setRecords(records);
// po.setPageSize(pageSize);
// po.setPageCurrent(pageCurrent);
//long pageCount=rowCount/pageSize;
// if(rowCount%pageSize!=0)pageCount++;
//po.setPageCount(pageCount);
//return po;
//return new PageObject<>(rowCount, records, pageCount, pageSize, pageCurrent);
return new PageObject<>(rowCount, records, pageSize, pageCurrent);
}
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.cy.pj.common.pojo.Node;
import com.cy.pj.sys.dao.SysMenuDao;
import com.cy.pj.sys.dao.SysRoleMenuDao;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.service.SysMenuService;
public class SysMenuServiceImpl implements SysMenuService{
private SysMenuDao sysMenuDao;
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
@Override
public int updateObject(SysMenu entity) {
//1.参数校验
if(entity==null)
throw new IllegalArgumentException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getName()))//org.springframework.util.StringUtils
throw new IllegalArgumentException("菜单名不允许为空");
//....
//2.保存菜单信息
int rows = sysMenuDao.updateObject(entity);
if(rows==0)
throw new ServiceException("记录可能已经不存在了");
return rows;
}
@Override
public int saveObject(SysMenu entity) {
//1.参数校验
if(entity==null)
throw new IllegalArgumentException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getName()))//org.springframework.util.StringUtils
throw new IllegalArgumentException("菜单名不允许为空");
//....
//2.保存菜单信息
int rows = sysMenuDao.insertObject(entity);
return rows;
}
@Override
public List
// ...
return sysMenuDao.findZtreeMenuNodes();
}
@Override
public int deleteObject(Integer id) {
// 1.参数校验
if(id==null||id<1)
throw new IllegalArgumentException("id值无效");
//2.查询子菜单个数
int childCount = sysMenuDao.getChildCount(id);
if(childCount>0)
throw new ServiceException("请先删除子菜单");
//3.删除菜单信息
//3.1删除关系数据
sysRoleMenuDao.deleteObjectsByMenuId(id);
//3.2删除自身信息
int rows = sysMenuDao.deleteObject(id);
if(rows==0)
throw new ServiceException("记录可能已经不存在了");
return rows;
}
// ....
return sysMenuDao.findObjects();
}
}
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.cy.pj.common.pojo.CheckBox;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.dao.SysRoleDao;
import com.cy.pj.sys.dao.SysRoleMenuDao;
import com.cy.pj.sys.pojo.SysRole;
import com.cy.pj.sys.pojo.SysRoleMenu;
import com.cy.pj.sys.service.SysRoleService;
public class SysRoleServiceImpl implements SysRoleService {
private SysRoleDao sysRoleDao;
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
@Override
public List
// ...
return sysRoleDao.findRoles();
}
@Override
public SysRoleMenu findObjectById(Integer id) {
// 1.参数校验
if(id==null||id<1)
throw new IllegalArgumentException("id值无效");
//2.基于角色id查询角色自身信息
SysRoleMenu rm = sysRoleDao.findObjectById(id);//sys_roles
//3.基于角色id查询角色对应的菜单id并进行封装
//List
//rm.setMenuIds(menuIds);
//4.返回查询结果
return rm;
}
@Override
public int updateObject(SysRole entity, Integer[] menuIds) {
// 1.参数校验
if(entity==null)
throw new IllegalArgumentException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getName()))//StringUtils为spring中的一个工具类
throw new IllegalArgumentException("角色名不能为空");
if(menuIds==null||menuIds.length==0)
throw new IllegalArgumentException("必须为角色分配权限");
//2.更新角色自身信息
int rows = sysRoleDao.updateObject(entity);
//3.更新角色菜单关系数据
//3.1删除原有关系数据
sysRoleMenuDao.deleteObjectsByRoleId(entity.getId());
//3.2添加新的关系数据
sysRoleMenuDao.insertObjects(entity.getId(), menuIds);
return rows;
}
@Override
public int saveObject(SysRole entity, Integer[] menuIds) {
// 1.参数校验
if(entity==null)
throw new IllegalArgumentException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getName()))//StringUtils为spring中的一个工具类
throw new IllegalArgumentException("角色名不能为空");
if(menuIds==null||menuIds.length==0)
throw new IllegalArgumentException("必须为角色分配权限");
//2.保存角色自身信息
int rows = sysRoleDao.insertObject(entity);
//3.保存角色菜单关系数据
sysRoleMenuDao.insertObjects(entity.getId(), menuIds);
return rows;
}
@Override
public PageObject
// 1.参数有效性校验
if(pageCurrent==null||pageCurrent<1)
throw new IllegalArgumentException("页码值无效");
//2.查询总记录数并校验
long rowCount = sysRoleDao.getRowCount(name);
if(rowCount==0)
throw new ServiceException("没有找到对应记录");
//3.查询当前页
int pageSize=2;
long startIndex=(pageCurrent-1)*pageSize;
List
sysRoleDao.findPageObjects(name, startIndex, pageSize);
//4.封装查询结果并返回
return new PageObject<>(rowCount, records, pageSize, pageCurrent);
}
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.common.utils.AssertUtils;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysRole;
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.pojo.SysUserDept;
import com.cy.pj.sys.service.SysUserService;
public class SysUserServiceImpl implements SysUserService {
private SysUserDao sysUserDao;
@Autowired
private SysUserRoleDao sysUserRoleDao;
@Override
public int updateObject(SysUser entity, Integer[] roleIds) {
// 1.参数校验
AssertUtils.isArgValid(entity==null, "保存对象不能为空");
AssertUtils.isArgValid(entity.getUsername()==null||"".equals(entity.getUsername()),"用户名不能为空");
AssertUtils.isArgValid(roleIds==null||roleIds.length==0, "必须为用户分配角色");
//2.保存用户自身信息
int rows = sysUserDao.updateObject(entity);
if(rows==0)
AssertUtils.isServiceValid(rows==0, "记录可能不存在");
//3.保存用户与角色关系数据
sysUserRoleDao.deleteObjectsByUserId(entity.getId());
sysUserRoleDao.insertObjects(entity.getId(), roleIds);
return rows;
}
@Override
public Map
// 1.参数校验
AssertUtils.isArgValid(id==null||id<1, "id值无效");
//2.查询用户以及用户对应的部门信息
SysUserDept user = sysUserDao.findObjectById(id);
AssertUtils.isServiceValid(user==null, "记录可能已经不存在");
//3.查询用户对应的角色信息
List
//4.封装查询结果
Map
map.put("user", user);
map.put("roleIds", roleIds);
return map;
}
@Override
public int saveObject(SysUser entity, Integer[] roleIds) {
// 1.参数校验
AssertUtils.isArgValid(entity==null, "保存对象不能为空");
AssertUtils.isArgValid(entity.getUsername()==null||"".equals(entity.getUsername()),"用户名不能为空");
AssertUtils.isArgValid(entity.getPassword()==null||"".equals(entity.getPassword()),"密码不能为空");
AssertUtils.isArgValid(roleIds==null||roleIds.length==0, "必须为用户分配角色");
//2.保存用户自身信息
//借助spring框架中自带API对密码进行加密
//2.1构建salt值
String salt = UUID.randomUUID().toString();//产生一个固定长度随机字符串
//2.2基于相关API对密码进行加密
//String hashedPassword =
//DigestUtils.md5DigestAsHex((salt+entity.getPassword()).getBytes());
//借助shiro框架中的API对密码进行加密
SimpleHash sh = new SimpleHash("MD5",//算法名称
entity.getPassword(), //未加密的密码
salt, //加密盐
1);//这里1表示加密次数
String hashedPassword = sh.toHex();//将加密结果转换为16进制的字符串
entity.setSalt(salt);//为什么盐值也要保存到数据库?(登陆时还要使用此salt对登陆密码进行加密)
entity.setPassword(hashedPassword);
//2.3将用户信息写入到数据库
int rows = sysUserDao.insertObject(entity);
//3.保存用户与角色关系数据
sysUserRoleDao.insertObjects(entity.getId(), roleIds);
return rows;
}
@Override
public int validById(Integer id, Integer valid) {
//1.参数校验
// if(id==null||id<1)
// throw new IllegalArgumentException("参数值无效");
// if(valid!=0&&valid!=1)
// throw new IllegalArgumentException("状态值无效");
AssertUtils.isArgValid(id==null||id<1, "参数值无效");
AssertUtils.isArgValid(valid!=0&&valid!=1, "状态值无效");
//2.修改用户状态
int rows = sysUserDao.validById(id, valid, "admin");//这里先假设为登陆用户
// if(rows==0)
// throw new ServiceException("记录可能已经不存在");
AssertUtils.isServiceValid(rows==0, "记录可能已经不存在");
return rows;
}
@Override
public PageObject
// 1.参数有效性校验
if(pageCurrent==null||pageCurrent<1)
throw new IllegalArgumentException("页码值无效");
//2.查询总记录数并校验
long rowCount = sysUserDao.getRowCount(username);
if(rowCount==0)
throw new ServiceException("没有找到对应记录");
//3.查询当前页
int pageSize=2;
long startIndex=(pageCurrent-1)*pageSize;
List
sysUserDao.findPageObjects(username, startIndex, pageSize);
//4.封装查询结果并返回
return new PageObject<>(rowCount, records, pageSize, pageCurrent);
}
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 基于Controller处理所有页面请求
* @author huawei
*
*/
@Controller
@RequestMapping("/")
public class PageController {
//http://localhost/log/log_list
//http://localhost/menu/menu_list
//rest风格的url,{}代表是变量表达式
@RequestMapping("{module}/{moduleUI}")
public String doModuleUI(@PathVariable String moduleUI) {
return "sys/"+moduleUI;
}
// @RequestMapping("menu/menu_list")
// public String doMenuUI() {
// return "sys/menu_list";
// }
//
//
// @RequestMapping("log/log_list")
// public String doLogUI() {
// return "sys/log_list";
// }
@RequestMapping("doPageUI")
public String doPageUI() {
return "common/page";
}
@RequestMapping("doIndexUI")
public String doIndexUI() {
return "starter";
}
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.cy.pj.sys.pojo.SysDept;
import com.cy.pj.sys.service.SysDeptService;
@RequestMapping("/dept/")
public class SysDeptController {
@Autowired
private SysDeptService sysDeptService;
@RequestMapping("doFindObjects")
public JsonResult doFindObjects() {
return new JsonResult(sysDeptService.findObjects());
}
@RequestMapping("doFindZTreeNodes")
public JsonResult doFindZTreeNodes() {
return new JsonResult(sysDeptService.findZTreeNodes());
}
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysDept entity){
sysDeptService.updateObject(entity);
return new JsonResult("update ok");
}
@RequestMapping("doSaveObject")
public JsonResult doSaveObject(SysDept entity){
sysDeptService.saveObject(entity);
return new JsonResult("save ok");
}
@RequestMapping("doDeleteObject")
@ResponseBody
public JsonResult doDeleteObject(Integer id){
sysDeptService.deleteObject(id);
return new JsonResult("delete ok");
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
@RequestMapping("/log/")
public class SysLogController {
private SysLogService sysLogService;
@RequestMapping("doDeleteObjects")
@ResponseBody
public JsonResult doDeleteObjects(Integer... ids) {
sysLogService.deleteObjects(ids);
return new JsonResult("delete ok");
}
@RequestMapping("doFindPageObjects")
@ResponseBody
public JsonResult doFindPageObjects(String username,Long pageCurrent) {
//try {
PageObject
sysLogService.findPageObjects(username, pageCurrent);
return new JsonResult(pageObject);
//} catch (Exception e) {
// return new JsonResult(e);
//}
}
/**
* 思考:假如控制层有多个方法,每个方法都要进行try操作,其实也是一个重复的操作
*/
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cy.pj.common.pojo.JsonResult;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.service.SysMenuService;
@RequestMapping("/menu/")
//@ResponseBody
@RestController//@Controller+@ResponseBody
public class SysMenuController {
private SysMenuService sysMenuService;
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysMenu entity) {
sysMenuService.updateObject(entity);
return new JsonResult("update ok");
}
@RequestMapping("doSaveObject")
public JsonResult doSaveObject(SysMenu entity) {
sysMenuService.saveObject(entity);
return new JsonResult("save ok");
}
@RequestMapping("doFindZtreeMenuNodes")
public JsonResult doFindZtreeMenuNodes() {
return new JsonResult(sysMenuService.findZtreeMenuNodes());
}
@RequestMapping("doDeleteObject")
public JsonResult doDeleteObject(Integer id) {
sysMenuService.deleteObject(id);
return new JsonResult("delete ok");
}
@RequestMapping("doFindObjects")
//@ResponseBody
public JsonResult doFindObjects() {
return new JsonResult(sysMenuService.findObjects());
}
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cy.pj.sys.pojo.SysRole;
import com.cy.pj.sys.service.SysRoleService;
@RequestMapping("/role/")
public class SysRoleController {
private SysRoleService sysRoleService;
@RequestMapping("doFindRoles")
public JsonResult doFindRoles() {
return new JsonResult(sysRoleService.findRoles());
}
@RequestMapping("doFindObjectById")
public JsonResult doFindObjectById(Integer id) {
return new JsonResult(sysRoleService.findObjectById(id));
}
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysRole entity,Integer[]menuIds) {
sysRoleService.updateObject(entity, menuIds);
return new JsonResult("save ok");
}
@RequestMapping("doSaveObject")
public JsonResult doSaveObject(SysRole entity,Integer[]menuIds) {
sysRoleService.saveObject(entity, menuIds);
return new JsonResult("save ok");
}
@RequestMapping("doFindPageObjects")
public JsonResult doFindPageObjects(String name,Long pageCurrent) {
return new JsonResult(sysRoleService.findPageObjects(name, pageCurrent));
}
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.service.SysUserService;
@RequestMapping("/user/")
public class SysUserController {
private SysUserService sysUserService;
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysUser entity,Integer[]roleIds) {
sysUserService.updateObject(entity, roleIds);
return new JsonResult("update ok");
}
@RequestMapping("doFindObjectById")
public JsonResult doFindObjectById(Integer id) {
return new JsonResult(sysUserService.findObjectById(id));
}
@RequestMapping("doSaveObject")
public JsonResult doSaveObject(SysUser entity,Integer[]roleIds) {
sysUserService.saveObject(entity, roleIds);
return new JsonResult("save ok");
}
@RequestMapping("doValidById")
public JsonResult doValidById(Integer id,Integer valid) {
sysUserService.validById(id, valid);
return new JsonResult("update ok");
}
@RequestMapping("doFindPageObjects")
public JsonResult doFindPageObjects(String username,Long pageCurrent) {
return new JsonResult(sysUserService.findPageObjects(username, pageCurrent));
}
}
import org.springframework.boot.autoconfigure.SpringBootApplication;
public class Application {
SpringApplication.run(Application.class, args);
}