1 日志治理设计说明
1.1 业务设计说明
本模块次要是实现对用户行为日志(例如谁在什么工夫点执行了什么操作,拜访了哪些办法,传递的什么参数,执行时长等)进行记录、查问、删除等操作。其表设计语句如下:
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='系统日志';
1.2 原型设计说明
基于用户需要,实现动态页面(html/css/js),通过动态页面为用户出现根本需要实现,如图-1所示。
图-1
阐明:如果客户对此原型进行了确认,后续则能够基于此原型进行研发。
1.3 API设计说明
日志业务后盾API分层架构及调用关系如图-2所示:
图-2
阐明:分层目标次要将简单问题简单化,实现各司其职,各尽所能。
2 日志治理列表页面出现
2.1 业务时序剖析
当点击首页左侧的"日志治理"菜单时,其总体时序剖析如图-3所示:
图-3
2.2 服务端实现
2.2.1 Controller实现
▪ 业务形容与设计实现
基于日志治理的申请业务,在PageController中增加doLogUI办法,doPageUI办法别离用于返回日志列表页面,日志分页页面。
▪ 要害代码设计与实现
第一步:在PageController中定义返回日志列表的办法。代码如下:
@GetMapping("doLogUI")public String doLogUI() {//页表页面 return "sys/log_list";//因为log_list是在pages/sys目录下}
第二步:在PageController中定义用于返回分页页面的办法。代码如下:
@GetMapping("doPageUI")public String doPageUI() { return "common/page";//分页页面}
2.3 客户端实现
2.3.1 日志菜单事件处理
▪ 业务形容与设计
首先筹备日志列表页面(/templates/pages/sys/log_list.html),而后在starter.html页面中点击日志治理菜单时异步加载日志列表页面。
▪ 要害代码设计与实现
找到我的项目中的starter.html 页面,页面加载实现当前,注册日志治理菜单项的点击事件,当点击日志治理时,执行事件处理函数。要害代码如下:
// 指定元素被点击会执行一个动作 $(function(){//jquery中ti提供的页面加载实现当前要执行的函数 doLoadUI("load-log-id","doLogUI") })//页面加载实现后执行//思考到代码的重用性(前面的模块也会这样写,太多了比拟繁杂),所以进行了封装 function doLoadUI(id,url){ $("#"+id).click(function(){ $("#mainContentId").load(url); }); }
其中,load函数为jquery中的ajax异步申请函数。
2.3.2 日志列表页面事件处理
▪ 业务形容与设计实现
当日志列表页面加载实现当前异步加载分页页面(page.html)。
▪ 要害代码设计与实现:
在log_list.html页面中异步加载page页面,这样能够实现分页页面重用,哪里须要分页页面,哪里就进行页面加载即可。要害代码如下:
$(function(){ $("#pageId").load("doPageUI");});
阐明:数据加载通常是一个绝对比拟耗时操作,为了改善用户体验,能够先为用户出现一个页面,数据加载时,显示数据正在加载中,数据加载实现当前再出现数据。这样也可满足现阶段不同类型客户端需要(例如手机端,电脑端,电视端,手表端。)
3 日志治理列表数据出现
3.1 数据架构剖析
日志查问服务端数据根本架构,如图-4所示。
图-4
3.2 服务端API架构及业务时序图剖析
服务端日志分页查问代码根本架构,如图-5所示:
图-5
服务端日志列表数据查问时序图,如图-6所示:
图-6
3.3 服务端要害业务及代码实现
3.3.1 Entity类实现
▪ 业务形容及设计实现
构建实体对象(POJO)封装从数据库查问到的记录,一行记录映射为内存中一个的这样的对象。对象属性定义时尽量与表中字段有肯定的映射关系,并增加对应的set/get/toString等办法,便于对数据进行更好的操作。
▪ 要害代码剖析及实现
package com.cy.pj.sys.pojo;import lombok.Data;import java.io.Serializable;import java.util.Date;/** 序列化:讲对象转化成字节的过程,当前便于通过网络进行传输或存储到相干介质中* 反序列化:将字节转换为对象的过程* */@Datapublic class SysLog implements Serializable { private static final long serialVersionUID = -1883432817460173462L; 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;}
阐明:通过此对象除了能够封装从数据库查问的数据,还能够封装客户端申请数据,实现层与层之间数据的传递。
思考:这个对象的set办法,get办法可能会在什么场景用到?
3.3.2 Dao接口实现
▪ 业务形容及设计实现
通过数据层对象,基于业务层参数数据查问日志记录总数以及当前页要出现的用户行为日志信息。
▪ 要害代码剖析及实现:
第一步:定义数据层接口对象,通过将此对象保障给业务层以提供日志数据拜访。代码如下:
@Mapperpublic interface SysLogDao { }
第二步:在SysLogDao接口中增加getRowCount办法用于按条件统计记录总数。代码如下:
/** * @param username 查问条件(例如查问哪个用户的日志信息) * @return 总记录数(基于这个后果能够计算总页数) */int getRowCount(@Param("username") String username);//查问日志总记录数
第三步:在SysLogDao接口中增加findPageObjects办法,基于此办法实现当前页记录的数据查问操作。代码如下:
/** * @param username 查问条件(例如查问哪个用户的日志信息) * @param startIndex 当前页的起始地位 * @param pageSize 当前页的页面大小 * @return 当前页的日志记录信息 * 数据库中每条日志信息封装到一个SysLog对象中 */List<SysLog> 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.3.3 Mapper文件实现
▪ 业务形容及设计实现
基于Dao接口创立映射文件,在此文件中通过相干元素(例如select)形容要执行的数据操作。
▪ 要害代码设计及实现
第一步:在映射文件的设计目录(mapper/sys)中增加SysLogMapper.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.SysLogDao"> </mapper>
第二步:在映射文件中增加sql元素实现,SQL中的共性操作,代码如下:
<sql id="queryWhereId"> from sys_logs <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"></include></select>
第四步:在映射文件中增加id为findPageObjects元素,实现分页查问。代码如下:
<select id="findPageObjects" resultType="com.cy.pj.sys.pojo.SysLog"> select * <include refid="queryWhereId"></include> order by createdTime desc limit #{startIndex},#{pageSize}</select>
思考:
1) 动静sql:基于用户需要动静拼接SQL
2) Sql标签元素的作用是什么?对sql语句中的共性进行提取,以遍实现更好的复用.
3) Include标签的作用是什么?引入应用sql标签定义的元素
第五步:单元测试类SysLogDaoTests,对数据层办法进行测试。
package com.cy.pj.sys.dao;import com.cy.pj.sys.pojo.SysLog;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTestpublic class SysLogDaoTests { @Autowired private SysLogDao sysLogDao; @Test void testFindPageObjects(){ List<SysLog> list=sysLogDao.findPageObjects("admin", 0, 3); for (SysLog log:list){ System.out.println(log); } } @Test void testSysLogDao(){ int rowCount = sysLogDao.getRowCount("admin"); System.out.println("rowCount="+rowCount); }}
3.3.4 Service接口及实现类
▪ 业务形容与设计实现
业务层次要是实现模块中业务逻辑的解决。在日志分页查问中,业务层对象首先要通过业务办法中的参数接管管制层数据(例如username,pageCurrent)并校验。而后基于用户名进行总记录数的查问并校验,再基于起始地位及页面大小进行当前页记录的查问,最初对查问后果进行封装并返回。
▪ 要害代码设计及实现
业务值对象定义,基于此对象封装数据层返回的数据以及计算的分页信息,具体代码参考如下:
package com.cy.pj.common.pojo;import com.cy.pj.sys.pojo.SysLog;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;import java.util.List;//借助此对象进行一个业务层的封装业务逻辑后果@Data@NoArgsConstructorpublic class PageObject<T> implements Serializable {//类名<泛型>==>类泛型:束缚类中属性,办法参数以及返回值类型 private static final long serialVersionUID = -5307436796659944757L; private Integer rowCount;//总记录数 private List<T> records;//当前页记录 private Integer pageCount;//总页数(计算出来) private Integer pageCurrent;//以后页码值 private Integer pageSize;//页面大小 public PageObject(Integer rowCount, List<T> records, Integer pageCurrent, Integer pageSize) { this.rowCount = rowCount; this.records = records; this.pageCurrent = pageCurrent; this.pageSize = pageSize; this.pageCount=rowCount/pageSize; if (this.rowCount%this.pageSize!=0)this.pageCount++; }}
定义日志业务接口及办法,裸露外界对日志业务数据的拜访,其代码参考如下:
package com.cy.pj.sys.service;import com.cy.pj.common.pojo.PageObject;import com.cy.pj.sys.pojo.SysLog;public interface SysLogService { /** * **@param** name 基于条件查问时的参数名 * **@param** pageCurrent 以后的页码值 * **@return** 当前页记录+分页信息 */ PageObject<SysLog> findPageObjects(String username,Integer pageCurrent);}
日志业务接口及实现类,用于具体执行日志业务数据的分页查问操作,其代码如下:
package com.cy.pj.sys.service.impl;import com.cy.pj.common.exception.ServiceException;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;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Servicepublic class SysLogServiceImpl implements SysLogService { @Autowired private SysLogDao sysLogDao; @Override public PageObject<SysLog> findPageObjects(String username, Integer pageCurrent) { //1.参数校验 if (pageCurrent==null||pageCurrent<1)throw new IllegalArgumentException("以后页码值不正确");
//2.基于条件查问总记录数
//2.1) 执行查问
//2.查问总记录数并校验int rowCount= sysLogDao.getRowCount(username);
//2.2) 验证查问后果,如果后果为0不再执行如下操作
if (rowCount==0) throw new ServiceException("没有找到对应记录");
//3.基于条件查问当前页记录(pageSize定义为2)
//3.1)定义pageSize
//3.查问dan当前页记录int pageSize=5;
//3.2)计算startIndex
int startIndex=(pageCurrent-1)*pageSize;
//3.3)执行以后数据的查问操作
List<SysLog> records=sysLogDao.findPageObjects(username, startIndex, pageSize);
//4.对分页信息以及当前页记录进行封装
//4.1)封装数据
package com.cy.pj.common.pojo;@Data@NoArgsConstructorpublic class PageObject<T> implements Serializable {private static final long serialVersionUID = -5307436796659944757L;// private Integer rowCount;//总记录数// private List<T> records;//当前页记录// private Integer pageCount;//总页数(计算出来)// private Integer pageCurrent;//以后页码值// private Integer pageSize;//页面大小//此局部就增加了上面public PageObject(Integer rowCount, List<T> records, Integer pageCurrent, Integer pageSize) { this.rowCount = rowCount; this.records = records; this.pageCurrent = pageCurrent; this.pageSize = pageSize; this.pageCount=rowCount/pageSize; if (this.rowCount%this.pageSize!=0)this.pageCount++;}
//4.2)调用所封装的数据
//封装查问后果并返回// PageObject<SysLog> po = new PageObject<>();// po.setRowCount(rowCount);// po.setRecords(records);// po.setPageCurrent(pageCurrent);// po.setPageSize(pageSize);// int pageCount = rowCount/pageSize;// if (rowCount%pageSize!=0)pageCount++;// po.setPageCount(pageCount);// return po; return new PageObject<>(rowCount,records,pageCurrent,pageSize); }}
在以后办法中须要的ServiceException是一个本人定义的异样, 通过自定义异样可更好的实现对业务问题的形容,同时能够更好的进步用户体验。参考代码如下:
package com.cy.pj.common.exception;public class ServiceException extends RuntimeException {// private static final long serialVersionUID = 5843835376260549700L; public ServiceException() { } public ServiceException(String message) { super(message); } public ServiceException(Throwable cause) { super(cause); }}
阐明:简直在所有的框架中都提供了自定义异样,例如MyBatis中的BindingException等。
定义Service对象的单元测试类,代码如下:
package com.cy.pj.sys.service;import com.cy.pj.common.pojo.PageObject;import com.cy.pj.sys.pojo.SysLog;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTestpublic class SysLogServiceTests { @Autowired private SysLogService sysLogService; @Test void testFindPageObjects(){ PageObject<SysLog> po = sysLogService.findPageObjects("admin", 1); System.out.println(po); }}
3.3.5 Controller类实现
▪ 业务形容与设计实现
管制层对象次要负责申请和响应数据的解决,例如,本模块首先要通过管制层对象解决申请参数,而后通过业务层对象执行业务逻辑,再通过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;
}
在Controller类中增加分页申请解决办法,代码参考如下:
@RequestMapping("doFindPageObjects")
@ResponseBody
public JsonResult doFindPageObjects(String username,Integer pageCurrent){
PageObject<SysLog> 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所示:
图-7
3.4 客户端要害业务及代码实现
3.4.1 客户端页面事件剖析
当用户点击首页日志治理时,其页面流转剖析如图-8所示:
图-8
3.4.2 日志列表信息出现
▪ 业务形容与设计实现
日志分页页面加载实现当前,向服务端发动异步申请加载日志信息,当日志信息加载实现须要将日志信息、分页信息出现到列表页面上。
▪ 要害代码设计与实现
第一步:分页页面加载实现,向服务端发动异步申请,代码参考如下:
$(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所示:
图-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=$("<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(data){
var tds="<td><input type='checkbox' class='cBox' name='cItem' value='"+data.id+"'></td>"+
"<td>"+data.username+"</td>"+
"<td>"+data.operation+"</td>"+
"<td>"+data.method+"</td>"+
"<td>"+data.params+"</td>"+
"<td>"+data.ip+"</td>"+
"<td>"+data.time+"</td>";
return tds;
}
3.4.3 分页数据信息出现
▪ 业务形容与设计实现
日志信息列表初始化实现当前初始化分页数据(调用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<pageCount){//下一页
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对象?对象
doHandleQueryResponseResult(result);
}
);//非凡的ajax函数 }
3.4.4 列表页面信息查问实现
▪ 业务形容及设计
当用户点击日志列表的查问按钮时,基于用户输出的用户名进行有条件的分页查问,并将查问后果出现在页面。
▪ 要害代码设计与实现:
第一步:日志列表页面加载实现,在查问按钮上进行事件注册。代码如下:
$(".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对象?对象
doHandleQueryResponseResult(result);
}
);
}
4 日志治理删除操作实现
4.1 数据架构剖析
当用户执行日志删除操作时,客户端与服务端交互时的根本数据架构,如图-10所示。
图-10
4.2 删除业务时序剖析
客户端提交删除申请,服务端对象的工作时序剖析,如图-11所示。
图-11
4.3 服务端要害业务及代码实现
4.3.1 Dao接口实现
▪ 业务形容及设计实现
数据层基于业务层提交的日志记录id,进行日志删除操作。
▪ 要害代码设计及实现:
在SysLogDao中增加基于id执行日志删除的办法。代码参考如下:
int deleteObjects(@Param("ids")Integer… ids);
4.3.2 Mapper文件实现
▪ 业务形容及设计实现
在SysLogDao接口对应的映射文件中增加用于执行删除业务的delete元素,此元素外部定义具体的SQL实现。
▪ 要害代码设计与实现
在SysLogMapper.xml文件增加delete元素,要害代码如下:
<delete id=_"deleteObjects"_>
delete from sys_Logs
where id in
<foreach collection=_"ids"_
open=_"("_
close=_")"_
separator=_","_
item=_"id"_>
#{id}
</foreach>
</delete>
FAQ剖析:如上SQL实现可能会存在什么问题?(可靠性问题,性能问题)
从可靠性的角度剖析,如果ids的值为null或长度为0时,SQL构建可能会呈现语法问题,可参考如下代码进行改良(先对ids的值进行断定):
<delete id="deleteObjects">
delete from sys_logs
<if test="ids!=null and ids.length>0">
where id in
<foreach collection="ids"
open="("
close=")"
separator=","
item="id">
#{id}
</foreach>
</if>
<if test="ids==null or ids.length==0">
where 1=2
</if>
</delete>
从SQL执行性能角度剖析,个别在SQL语句中不倡议应用in表达式,能够参考如下代码进行实现(重点是forearch中or运算符的利用):
<delete id=_"deleteObjects"_>
delete from sys_logs
<choose>
<when test=_"ids!=null and ids.length>0"_>
<where>
<foreach collection=_"ids"_
item=_"id"_
separator=_"or"_>
id=#{id}
</foreach>
</where>
</when>
<otherwise>
where 1=2
</otherwise>
</choose>
</delete>
阐明:这里的choose元素也为一种抉择构造,when元素相当于if,otherwise相当于else的语法。
4.3.3 Service接口及实现类
▪ 业务形容与设计实现
在日志业务层定义用于执行删除业务的办法,首先通过办法参数接管管制层传递的多个记录的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;
}
4.3.4 Controller类实现
▪ 业务形容与设计实现
在日志管制层对象中,增加用于解决日志删除申请的办法。首先在此办法中通过形参接管客户端提交的数据,而后调用业务层对象执行删除操作,最初封装执行后果,并在运行时将响应对象转换为JSON格局的字符串,响应到客户端。
▪ 要害代码设计与实现
第一步:在SysLogController中增加用于执行删除业务的办法。代码如下:
@RequestMapping("doDeleteObjects")
@ResponseBody
public JsonResult doDeleteObjects(Integer… ids){
sysLogService.deleteObjects(ids);
return new JsonResult("delete ok");
}
第二步:启动tomcat进行拜访测试,关上浏览器输出如下网址:
http://localhost/log/doDelete...
4.4 客户端要害业务及代码实现
4.4.1 日志列表页面事件处理
▪ 业务形容及设计实现
用户在页面上首先抉择要删除的元素,而后点击删除按钮,将用户抉择的记录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();
}
阐明:最初将如上办法增加在删除操作胜利当前的代码块中。
5 日志治理数据增加实现
5.1 服务端要害业务及代码实现
这块业务学了AOP当前再实现.
5.1.1 Dao接口实现
▪ 业务形容与设计实现
数据层基于业务层的长久化申请,将业务层提交的用户行为日志信息写入到数据库。
▪ 要害代码设计与实现
在SysLogDao接口中增加用于实现日志信息长久化的办法。要害代码如下:
int insertObject(SysLog entity);
5.1.2 Mapper映射文件
▪ 业务形容与设计实现
基于SysLogDao中办法的定义,编写用于数据长久化的SQL元素。
▪ 要害代码设计与实现
在SysLogMapper.xml中增加insertObject元素,用于向日志表写入用户行为日志。要害代码如下:
<insert id="insertObject">
insert into sys_logs
(username,operation,method,params,time,ip,createdTime)
values
(#{username},#{operation},#{method},#{params},#{time},#{ip},#{createdTime})
</insert>
5.1.3 Service接口及实现类
▪ 业务形容与设计实现
将日志切面中抓取到的用户行为日志信息,通过业务层对象办法长久化到数据库。
▪ 要害代码实现
第一步:在SysLogService接口中,增加保留日志信息的办法。要害代码如下:
void saveObject(SysLog entity)
第二步:在SysLogServiceImpl类中增加,保留日志的办法实现。要害代码如下:
@Override
public void saveObject(SysLog entity) {
sysLogDao.insertObject(entity);
}
5.1.4 日志切面Aspect实现
▪ 业务形容与设计实现
在日志切面中,抓取用户行为信息,并将其封装到日志对象而后传递到业务,通过业务层对象对日志日志信息做进一步解决。此局部内容后续联合AOP进行实现(临时先理解,不做具体实现)。
▪ 要害代码设计与实现
定义日志切面类对象,通过盘绕告诉解决日志记录操作。要害代码如下:
@Aspect
@Component
public class SysLogAspect {
private Logger log=LoggerFactory._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.saveObject(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所示:
图-12
6 总结
6.1 重难点剖析
▪ 日志治理整体业务剖析与实现。
1) 分层架构(应用层MVC:基于spring的mvc模块)。
2) API架构(SysLogDao,SysLogService,SysLogController)。
3) 业务架构(查问,删除,增加用户行为日志)。
4) 数据架构(SysLog,PageObject,JsonResult,..)。
▪ 日志治理长久层映射文件中SQL元素的定义及编写。
1) 定义在映射文件”mapper/sys/SysLogMapper.xml”(必须在加载范畴内)。
2) 每个SQL元素必须提供一个惟一ID,对于select必须指定后果映射(resultType)。
3) 零碎底层运行时会将每个SQL元素的对象封装一个值对象
(MappedStatement)。
▪ 日志治理模块数据查问操作中的数据封装。
1) 数据层(数据逻辑)的SysLog对象利用(一行记录一个log对象)。
2) 业务层(业务逻辑)PageObject对象利用(封装每页记录以及对应的分页信息)。
3) 管制层(管制逻辑)的JsonResult对象利用(对业务数据增加状态信息)。
▪ 日志管理控制层申请数据映射,响应数据的封装及转换(转换为json 串)。
1) 申请门路映射,申请形式映射(GET,POST),申请参数映射(间接量,POJO)。
2) 响应数据两种(页面,JSON串)。
▪ 日志治理模块异样解决如何实现的。
1) 申请解决层(管制层)定义对立(全局)异样解决类。
2) 应用注解@ControllerAdvice形容类,应用@ExceptionHandler形容办法.
3) 异样解决规定:能解决则解决,不能解决则抛出。
6.2 FAQ剖析
▪ 用户行为日志表中都有哪些字段?(面试时有时会问)
▪ 用户行为日志是如何实现分页查问的?(limit)
▪ 用户行为数据的封装过程?(数据层,业务层,管制层)
▪ 我的项目中的异样是如何解决的?
▪ 页面中数据乱码,如何解决?(数据起源,申请数据,响应数据)
▪ 说说的日志删除业务是如何实现?
▪ Spring MVC 响应数据处理?(view,json)
▪ 我的项目你罕用的JS函数说几个?(data,prop,ajax,each,..)
▪ MyBatis中的@Params注解的作用?(为参数变量指定其其别名)
▪ Jquery中data函数用于做什么?能够借助data函数将数据绑定到指定对象,语法为data(key[,value]),key和value为本人业务中的任意数据,如果只有key示意取值。
▪ Jquery中的prop函数用于获取html标签对象中”规范属性”的值或为属性赋值,其语法为prop(propertyName[,propertyValue]),如果只有属性名则为获取属性值。
▪ Jquery中attr函数为用户获取html标签中任意属性值或为属性赋值的一个办法,其语法为attr(propertyName[,propertyValue]),如果只有属性名则为获取属性值。
▪ 日志写操作事务的流传个性如何配置?(每次开启新事务)?
▪ 日志写操作为什么应该是异步的?
▪ Spring 中的异步操作如何实现?
▪ Spring 中的@Async如何利用?
▪ 我的项目中的BUG剖析及解决套路?(排除法,打桩,断点)
6.3 BUG剖析
▪ 无奈找到对应的Bean对象(NoSuchBeanDefinitionException),如图-13所示:
图-13
问题剖析:
1) 检测key的名字写的是否正确。
2) 检测spring对此Bean对象的扫描,对于dao而言。
3) 应用有@Mapper注解形容或者在@MapperScan扫描范畴之内。
4) 以上都正确,要检测是否编译了。
▪ 绑定异样(BindingException),如图-14所示。
图-14
问题剖析:
1) 接口的类全名与对应的映射文件命名空间不同。
2) 接口的办法名与对应的映射文件元素名不存在。
3) 检测映射文件的门路与application.properties或者application.yml中的配置是否统一。
4) 以上都没有问题时,检测你的类和映射文件是否失常编译。
▪ 反射异样(ReflectionException),如图-15所示
图-15
问题剖析:
1) 映射文件中动静sql中应用的参数在接口办法中没有应用@Param注解润饰
2) 如果应用了注解润饰还要检测名字是否统一。
阐明:当动静sql的参数在接口中没有应用@Param注解润饰,还能够借助_parameter这个变量获取参数的值(mybatis中的一种标准)。
▪ 后果映射异样,如图-16所示:
图-16
问题剖析:getRowCount元素可能没有写resultType或resultMap。
▪ 绑定异样,如图-17所示:
图-17
问题剖析:绑定异样,检测findPageObjects办法参数与映射文件参数名字是否匹配或者如果版本不是最新版本须要应用@Param注解形容。
▪ Bean创立异样,如图-18所示
图-18
问题剖析:应该是查问时的后果映射对的类全名写错了。
▪ 申请形式不匹配,如图-19示
图-19
问题剖析:申请形式与管制层解决形式不匹配。
▪ 响应后果异样,如图-20所示:
图-20
问题剖析:服务端响应数据不正确,例如服务端没有注册将对象转换为JSON串的Bean
▪ 申请参数异样,如图-21示:
图-21
问题剖析:客户端申请参数中不蕴含服务端管制层办法参数或格局不匹配。
▪ JS编写谬误,如图-22所示:
图-22
问题剖析:点击右侧VM176:64地位进行查看。
▪ JS编写谬误,如图-23所示:
图-23
问题剖析:找到对应地位,检测data的值以及数据起源。
▪ JS编写谬误,如图-24所示:
图-24
问题剖析:找到对应地位,如果无奈确定地位,可排除法或打桩,debug剖析。
▪ JS写谬误,如图-25所示:
图-25
问题剖析:调用length办法的对象有问题,可先检测下对象的值。
▪ JS编写谬误,如图-26所示:
图-26
问题剖析:检测record定义或赋值的中央。
▪ 资源没找到,如图-27所示:
图-27
问题剖析:服务端资源没找到,查看url和controller映射,不要点击图中的jquery。
▪ 视图解析异样,如图-28所示:
图-28
问题剖析:查看服务端要拜访的办法上是否有@ResponseBody注解.