动吧项目
权限管理子系统
日志管理

1. 日志管理概要设计 1-2
1.1. 原型设计 1-2
1.2. API架构设计 1-3
2. 日志管理列表页面呈现 2-4
2.1. 服务端实现 2-4
2.1.1. Controller实现 2-4
2.2. 客户端实现 2-5
2.2.1. 首页页面日志管理事件处理 2-5
2.2.2. 创建日志列表页面 2-6
3. 日志管理列表数据呈现 3-7
3.1. 服务端实现 3-7
3.1.1. Entity类实现 3-8
3.1.2. Dao接口实现 3-11
3.1.3. Mapper文件实现 3-12
3.1.4. Service接口及实现类 3-14
3.1.5. Controller类的实现 3-18
3.2. 客户端实现 3-21
3.2.1. 日志信息呈现 3-22
3.2.2. 分页信息呈现 3-24
3.2.3. 列表页面信息查询实现 3-25
4. 日志管理删除操作实现 4-26
4.1. 服务端的实现 4-26
4.1.1. Dao接口实现 4-27
4.1.2. Mapper文件实现 4-27
4.1.3. Service接口实现 4-28
4.1.4. Controller实现 4-29
4.2. 客户端的实现 4-30
4.2.1. 日志列表页面实现 4-30
5. 日志管理数据添加实现 5-32
5.1. 服务端实现 5-32
5.1.1. Dao实现 5-32
5.1.2. Mapper实现 5-33
5.1.3. 日志切面实现 5-33
6. 总结 6-36
6.1. 重点和难点分析 6-36
6.2. 问题分析 6-36
6.3. 原理分析 6-37
6.3.1. Mybatis 原理分析(了解) 6-37
6.3.2. Spring相关原理分析 6-38
6.3.3. Spring MVC 原理分析: 6-39
6.3.4. Tomcat 启动流分析(注解方式-先不用关心) 6-40
6.4. 常见FAQ 6-41
6.5. BUG分析 6-42

日志管理概要设计

原型设计

基于用户需求,实现静态页面,通过静态页面为用户呈现基本需求实现。

日志管理 - 图1

API架构设计

日志业务后台应用架构

日志管理 - 图2

代码基本结构:(水平分层架构)
日志管理 - 图3

数据基本结构(以日志分页查询为例)

日志管理 - 图4

日志管理列表页面呈现

服务端实现

Controller实现

业务描述

  1. 定义日志管理Controller处理日志管理的客户端请求

  2. 修改PageController添加doPageUI方法返回page页面

业务实现

  1. 日志管理Controller类的定义

  2. 包名 com.db.sys.controller

  3. 类名SysLogController

  4. 映射 “/log/”

  1. 日志管理Controller中方法定义(添加返回页面的相关方法)

  2. 方法名 doLogListUI

  3. 参数列表(无)

  4. 返回值类型:String

  5. Url映射:doLogListUI

代码实现:

日志Controller实现
@Controller
@RequestMapping(“/log/“)
public class SysLogController {
@RequestMapping(“doLogListUI”)
public String doLogListUI(){
return “sys/log_list”;
}
}

页面PageController中实现

@RequestMapping(“doPageUI”)
public String doPageUI(){
return “common/page”;
}

客户端实现

首页页面日志管理事件处理

页面描述

  1. 准备日志列表页面(WEB-INF/pages/sys/log_list.html)

  2. starter.html页面中点击日志管理菜单时异步加载日志列表页面。

业务实现

  1. 事件注册(被点击的元素上)

  2. 事件处理函数定义

代码实现:starter.html

$(function(){
$(“#load-log-id”).click(function(){
$(“#mainContentId”).load(“log/doLogListUI.do”);
});
})

load函数为jquery中的ajax异步请求函数。

代码分析:
日志管理 - 图5

创建日志列表页面

业务描述
1) 在WEB-INF/pages/sys目录下定义log_list.html页面.
2) 当页面加载完成以后异步加载分页页面(page.html)。

业务实现

  1. 在PageController中添加doPageUI方法返回page页面(假如已有则无须再次添加)

  2. 在log_list.html页面中异步加载page页面页面

关键代码实现:log_list.html

$(function(){
$(“#pageId”).load(“doPageUI.do”);
});

思考:为什么要先呈现页面,后呈现数据,而不是页面和数据一起呈现。

数据加载通常是一个相对比较耗时操作,为了改善用户体验,可以先为
用户呈现一个页面,数据加载时,显示数据正在加载中。

日志管理列表数据呈现

日志列表数据查询时序图如下:
日志管理 - 图6

服务端实现

服务端日志分页查询代码基本架构

日志管理 - 图7

服务端分页查询数据基本架构

日志管理 - 图8

Entity类实现

业务描述:
定义实体对象(POJO)封装从数据库查询的数据实现ORM映射
业务实现
构建与sys_Logs表对应的实体类型

  1. 包名 com.db.sys.entity

  2. 类名 SysLog (实现序列化接口,并定义序列化id)

  3. 属性 与表(sys_Logs)中字段有对应关系

  4. 方法 set/get/toString

关键代码实现:

package com.db.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;

  1. /**设置:*/<br /> **public** **void** setId(Integer id) {<br /> **this**.id = id;<br /> }<br /> /**获取:*/<br /> **public** Integer getId() {<br /> **return** id;<br /> }<br /> /**设置:用户名*/<br /> **public** **void** setUsername(String username) {<br /> **this**.username = username;<br /> }<br /> /** 获取:用户名*/<br /> **public** String getUsername() {<br /> **return** username;<br /> }<br /> /**设置:用户操作*/<br /> **public** **void** setOperation(String operation) {<br /> **this**.operation = operation;<br /> }<br /> /**获取:用户操作*/<br /> **public** String getOperation() {<br /> **return** operation;<br /> }<br /> /**设置:请求方法*/<br /> **public** **void** setMethod(String method) {<br /> **this**.method = method;<br /> }<br /> /**获取:请求方法*/<br /> **public** String getMethod() {<br /> **return** method;<br /> }<br /> /** 设置:请求参数*/<br /> **public** **void** setParams(String params) {<br /> **this**.params = params;<br /> }<br /> /** 获取:请求参数 */<br /> **public** String getParams() {<br /> **return** params;<br /> }<br /> /**设置:IP地址 */<br /> **public** **void** setIp(String ip) {<br /> **this**.ip = ip;<br /> }<br /> /** 获取:IP地址*/<br /> **public** String getIp() {<br /> **return** ip;<br /> }<br /> /** 设置:创建时间*/<br /> **public** **void** setCreateDate(Date createdTime) {<br /> **this**.createdTime = createdTime;<br /> }<br /> /** 获取:创建时间*/<br /> **public** Date getCreatedTime() {<br /> **return** createdTime;<br /> }
  2. **public** Long getTime() {<br /> **return** time;<br /> }
  3. **public** **void** setTime(Long time) {<br /> **this**.time = time;<br /> }<br />}

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

Dao接口实现

业务描述:(核心-查询当前页显示的数据以及总记录数)

  1. 接收业务层参数数据

  2. 基于参数进行数据查询

  3. 将查询结果进行封装

  4. 将结果返回给业务层对象

代码实现:创建接口并定义相关方法。

  1. Dao接口定义

  2. 包名: com.db.sys.dao

  3. 名字: SysLogDao

  1. 方法定义:负责基于条件查询当前页数据

  2. 方法名:findPageObjects

  3. 参数列表:(String username,Integer startIndex,Integer pageSize)

  4. 返回值:List

  1. 方法定义:负责基于条件查询总记录数

  2. 方法名:getRowCount

  3. 参数列表:(String username)

  4. 返回值:int

代码实现:

public interface SysLogDao {
/
基于条件分页查询日志信息
@param username 查询条件(例如查询哪个用户的日志信息)
*
@param startIndex 当前页的起始位置
*
@param pageSize 当前页的页面大小
*
@return 当前页的日志记录信息
数据库中每条日志信息封装到一个SysLog对象中
/
List findPageObjects(
@Param(“username”)String username,
@Param(“startIndex”)Integer startIndex,
@Param(“pageSize”)Integer pageSize);
/

基于条件查询总记录数
@param username 查询条件(例如查询哪个用户的日志信息)
@return 总记录数(基于这个结果可以计算总页数)
说明:假如如下方法没有使用注解修饰,在基于名字进行查询
时候会出现There is no getter for property named
‘username’ in ‘class java.lang.String’
/
*int
getRowCount(@Param(“username”) String username);

}

说明:

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

  2. 当DAO方法中的参数应用在动态SQL中时无论多少个参数,尽量使用@Param注解进行修饰并定义。

Mapper文件实现

业务描述

  1. 基于Dao接口创建映射文件

  2. 基于Dao方法在映射文件中创建映射元素建映射元素

业务实现:

  1. 创建映射文件

  2. 包名:mapper.sys

  3. 文件名:SysLogMapper.xml

  4. 命名空间 com.db.sys.dao.SysLogDao

  1. 创建映射元素实现翻页查询操作

  2. 元素名 select

  3. 元素id findPageObjects

  4. 参数类型 (不写)

  5. 结果类型 com.db.sys.entity.SysLog

  6. SQL定义 select * from sys_Logs where name like ? limit ?,?

  1. 创建映射元素实现查询统计操作

  2. 元素名 select

  3. 元素id getRowCount

  4. 参数类型 (不写)

  5. 结果类型 int

  6. SQL定义 select count(*) from sys_Logs where name like ?

代码实现:

<?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">


resultType=“com.db.sys.entity.SysLog”>
select
from sysLogs
<include refid=
“queryWhereId”/>
order by createdTime desc
limit #{startIndex},#{pageSize}


<select id=
“getRowCount”
resultType=
“int”_>
select count(
)
from sysLogs
<include refid=
“queryWhereId”/>


<sql id=
“queryWhereId”>

<if test=
“username!=null and username!=’’”_>
username like concat(“%”,#{username},”%”)



思考:

  1. 动态sql:基于用于需求动态拼接SQL

  2. Sql标签元素的作用是什么?对sql语句中的共性进行提取,以遍实现更好的复用.

  3. Include标签的作用是什么?引入使用sql标签定义的元素

说明:
当不记得数据库中有哪些函数时可以借助 ? functions 的方式进行查询.其中这个?等价于help单词.

Service接口及实现类

业务描述:核心业务就是分页查询数据并对数据进行封装。

  1. 通过参数变量接收控制层数据(例如username,pageCurrent)

  2. 对数据进行合法验证(例如pageCurrent不能小于1)

  3. 基于参数数据进行总记录数查询(通过此结果要计算总页数)

  4. 基于参数数据进行当前页记录的查询(username,startIndex,pageSize)

  5. 对数据进行封装(例如封装分页信息和当前页记录)

  6. ……….

业务实现:

  1. 业务值对象定义:(封装分页信息以及当前数据)

  2. 包名 com.db.common.vo (封装值的对象)

  3. 类名 PageObject (实现序列化接口并添加序列化id)

  4. 属性 (总行数,总页数,当前页码,页面大小,当前页记录信息)

  5. 方法 (set/get)

代码实现:

public class PageObject implements Serializable {
private static final long serialVersionUID = 6780580291247550747L;//类泛型
/当前页的页码值*/
private Integer pageCurrent=1;
/
页面大小/
private Integer pageSize=3;
/**总行数(通过查询获得)
/
private Integer rowCount=0;
/总页数(通过计算获得)*/
private Integer pageCount=0;
/
当前页记录/
private List records;
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 getRecords() {
return records;
}
public void setRecords(List records) {
*this
.records = records;
}
}

  1. 接口定义:为控制层请求提供服务

  2. 包名 com.db.sys.service

  3. 类名 SysLogService

  1. 接口方法定义:

  2. 方法名 findPageObjects

  3. 参数列表 (String name,Integer pageCurrent)

  4. 返回值 PageObject

关键代码实现:

public interface SysLogService {
/
通过此方法实现分页查询操作
@param name 基于条件查询时的参数名
*
@param pageCurrent 当前的页码值
*
@return* 当前页记录+分页信息
/
PageObject findPageObjects( String username, Integer pageCurrent);
}

  1. 接口实现类的定义:

  2. 包名 com.db.sys.service.impl

  3. 类名 SysLogServiceImpl

代码实现

@Service
public class SysLogServiceImpl implements SysLogService{
@Autowired
private SysLogDao sysLogDao;
@Override
public PageObject findPageObjects( 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 records=
sysLogDao.findPageObjects(name, startIndex, pageSize);
//4.对分页信息以及当前页记录进行封装
//4.1)构建PageObject对象
PageObject pageObject=new 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.db.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
}
}

说明:通过自定义异常可更好的定位错误,确定问题以便提供用户体验。

Controller类的实现

业务描述:核心业务是处理客户端请求

  1. 接收客户端请求中的数据

  2. 基于请求调用业务方法进行请求处理

  3. 对处理结果进行封装(JsonResult)

  4. 将结果转换为json格式的字符串(适配不同客户端)

  5. 将字符串通过服务器输出到客户端。

业务实现:

  1. 值对象定义:(封装控制层方法的返回结果)

  2. 包名: com.db.common.vo

  3. 类名: JsonResult

  4. 属性: (状态码,状态信息,正确数据)

  5. 方法: (set/get,构造方法)

  1. Controller方法定义:(在SysLogController中定义请求处理方法)

  2. 方法名 doFindPageObjects

  3. 参数列表 (String name,Integer pageCurrent)

  4. 返回值 JsonResult

  5. 映射 doFindPageObjects

  1. 统一异常处理类的定义

  2. 包名 com.db.common.web

  3. 类名 GlobalExceptionHandler

  4. 注解 @ControllerAdvice

  1. 异常类中方法定义(处理参数异常的方法)

  2. 方法名 doHandleRuntimeException

  3. 参数列表 RuntimeException

  4. 返回值 JsonResult

  5. 注解描述(@ResponseBody,@ExceptionHandler)

关键代码实现:

封装控制层值的对象

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方法定义

@RequestMapping(“doFindPageObjects”)
@ResponseBody
public JsonResult doFindPageObjects(String name,Integer pageCurrent){
PageObject pageObject=
sysLogService.findPageObjects(name,pageCurrent);
return new JsonResult(pageObject);
}

统一异常类及方法定义

package com.db.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.db.common.vo.JsonResult;
/全局异常处理类*/
@ControllerAdvice
public class GlobalExceptionHandler {
//JDK中的自带的日志API
@ExceptionHandler(RuntimeException.
class)
@ResponseBody
public JsonResult doHandleRuntimeException(
RuntimeException e){
e.printStackTrace();//也可以写日志
return new** JsonResult(e);//封装异常信息
}
}

控制层数据分析

日志管理 - 图9

启动tomcat进行访问测试,其时序图如下:

日志管理 - 图10

客户端实现

客户端数据流转分析

日志管理 - 图11

日志信息呈现

业务描述:

  1. 列表页面加载完成发起异步请求加载日志信息

  2. 通过服务端返回的数据更新当前列表页面

业务实现

  1. 定义doGetObjects()函数,通过此函数执行异步加载操作。

  2. 分页页面加载完成以后调用doGetObjects().

关键代码实现:

$(function(){
//为什么要将doGetObjects函数写到load函数对应的回调内部。
$(“#pageId”).load(“doPageUI.do”,function(){
doGetObjects();
});
}

分页查询函数定义
function doGetObjects(){
//debugger;//断点调试
//1.定义url和参数
var url=”log/doFindPageObjects.do”
//? 请问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){
//JsonResult->PageObject->List+…
//请问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=$(““);
//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=”“+
““+data.username+”“+
““+data.operation+”“+
““+data.method+”“+
““+data.params+”“+
““+data.ip+”“+
““+data.time+”“;
return tds;
}

分页信息呈现

业务描述

  1. 列表日志信息异步加载完成以后初始化分页数据(调用setPagination函数)

  2. 点击分页元素时异步加载当前页(pageCurrent)数据(调用jumpToPage)

业务实现 (page.html页面中定义JS函数)

  1. 定义doSetPagination方法(实现分页数据初始化)

  2. 定义doJumpToPage方法(通过此方法实现当前数据查询)

  3. page.html页面加载完成以后在对应元素上注册click事件

关键代码实现:

$(function(){
//事件注册
$(“#pageId”).on(“click”,”.first,.pre,.next,.last”,doJumpToPage);
})

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);
}
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;
}
//3.对pageCurrent值进行重新绑定
$(“#pageId”).data(“pageCurrent”,pageCurrent);
//4.基于新的pageCurrent的值进行当前页数据查询
doGetObjects();
}

列表页面信息查询实现

业务说明

  1. 列表查询按钮事件注册

  2. 列表查询按钮事件处理函数定义

  3. 列表查询参数获取以及传递

业务实现:

  1. 在$(function(){})回调函数中追加查询按钮的事件注册。

  2. 定义查询按钮的事件处理函数doQueryObjects

  3. 重用doGetObjects函数并添加查询参数name

关键代码实现:

查询按钮事件注册
$(“.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.do”
//? 请问data函数的含义是什么?(从指定元素上获取绑定的数据)
//此数据会在何时进行绑定?(setPagination,doQueryObjects)
var pageCurrent=$(“#pageId”).data(“pageCurrent”);
//为什么要执行如下语句的判定,然后初始化pageCurrent的值为1
//pageCurrent参数在没有赋值的情况下,默认初始值应该为1.
if(!pageCurrent) pageCurrent=1;
var params={“pageCurrent”:pageCurrent};
//为什么此位置要获取查询参数的值?
//一种冗余的应用方法,目的时让此函数在查询时可以重用。
var username=$(“#searchNameId”).val();
//如下语句的含义是什么?动态在js对象中添加key/value,
if(username) params.username=username;//查询时需要
//2.发起异步请求
//请问如下ajax请求的回调函数参数名可以是任意吗?可以,必须符合标识符的规范
$.getJSON(url,params,function(result){
//请问result是一个字符串还是json格式的js对象?对象
doHandleResponseResult(result);
}
);
}

日志管理删除操作实现

服务端的实现

业务时序图:

日志管理 - 图12

Dao接口实现

业务描述

  1. 接收业务层参数数据(Integer… ids)

  2. 基于数据执行删除操作

业务实现
在SysLogDao接口中定义删除方法

  1. 方法名 deleteObjects

  2. 方法参数 (Integer… ids)

  3. 方法返回值 int

关键代码实现:

int deleteObjects(@Param(“ids”)Integer… ids);

Mapper文件实现

业务描述
基于SysLogDao中方法的定义,编写删除元素。
业务实现:
在SysLogMapper.xml文件中定义删除元素

  1. 元素名 delete

  2. 元素id deleteObjects

  3. Sql定义 : delete from sys_Logs where id in (?,?,?)

关键代码实现:

<delete id=_"deleteObjects"_><br />       delete from sys_Logs<br />       where id in <br />       <foreach collection=_"ids"_<br />               open=_"("_<br />               close=_")"_<br />               separator=_","_<br />               item=_"id"_><br />               #{id} <br />        </foreach><br />    </delete>

Service接口实现

业务描述

  1. 接收控制层数据并进行合法验证

  2. 基于业务层数据执行删除操作

  3. 对删除过程进行监控(先进行异常捕获)

  4. 对删除结果进行验证并返回

业务实现
在SysLogService接口及实现类中添加方法

  1. 方法名 deleteObjects

  2. 参数列表 Integer… ids

  3. 返回值 int

关键代码实现:

@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;
}

Controller实现

业务描述

  1. 接收客户端请求数据

  2. 调用业务层方法执行删除操作

  3. 封装结果并返回

业务实现
在SysLogController中定义删除方法

  1. 方法名 doDeleteObjects

  2. 参数列表 Integer ids

  3. 返回值 JsonResult

  4. 映射 doDeleteObjects

关键代码实现:

  @RequestMapping("doDeleteObjects")<br />      @ResponseBody<br />      **public** JsonResult doDeleteObjects(Integer… ids){<br />          sysLogService.deleteObjects(ids);<br />          **return** **new** JsonResult("delete ok");<br />      }

启动tomcat进行访问测试:
http://localhost/CGB-DB-SYS-V2.01/log/doDeleteObjects.do?ids=1,2,3

客户端的实现

日志列表页面实现

业务描述:

  1. 页面全选操作实现

  2. 页面删除按钮事件注册

  3. 页面删除操作事件处理函数定义

业务实现:

  1. Thead中全选checkbox元素事件注册及事件处理函数doCheckAll定义

  2. Tbody中checkbox元素事件注册及事件处理函数doChangeCheckAllState定义

  3. 在$(function(){})回调函数中追加删除按钮的事件注册操作。

  4. 定义事件处理函数doDeleteObjects,处理删除按钮的点击操作。

关键代码实现

Tbody中checkbox的状态影响thead中全选元素的状态
//当tbody中checkbox的状态发生变化以后
//修改thead中全选元素的状态值。
function doChangeTHeadCheckBoxState(){
//1.设定默认状态值
var flag=true;
//2.迭代所有tbody中的checkbox值并进行与操作
$(“#tbodyId input[name=’cItem’]”)
.each(function(){
flag=flag&$(this).prop(“checked”)
});
//3.修改全选元素checkbox的值为flag
$(“#checkAll”).prop(“checked”,flag);
}

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[name=’cItem’]”)
.prop(“checked”,flag);
}

删除按钮事件处理函数定义
/执行删除操作/
function doDeleteObjects(){
//1.获取选中的id值
var ids=doGetCheckedIds();
if(ids.length==0){
alert(“至少选择一个”);
return;
}
//2.发异步请求执行删除操作
var url=”Log/doDeleteObjects.do”;
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;
}

日志管理数据添加实现

服务端实现

学了Spring AOP以后再做这个添加业务

Dao实现

业务描述:

  1. 接收业务层数据(SysLog)

  2. 将数据写入到数据库(ORM)

业务实现:在SysLogDao接口中定义方法

  1. 方法名 insertObject

  2. 参数列表 SysLog entity

  3. 返回值 int

关键代码实现:

int insertObject(SysLog entity);

Mapper实现

业务描述
基于SysLogDao中方法的定义编写SQL元素。

业务实现:
在SysLogMapper文件中定义insert元素

  1. 元素名 insert

  2. 元素id insertObject

  3. Sql定义: insert into sys_Logs (…) values(?,?,….)

关键代码实现:


insert into sys_logs
(username,operation,method,params,time,ip,createdTime)
values
(#{username},#{operation},#{method},#{params},#{time},#{ip},#{createdTime})

日志切面实现

此部分内容后续结合AOP进行实现(暂时先了解,不做具体实现)

@Aspect
@Component
public class SysLogAspect {
private Logger log=Logger.getLogger(SysLogAspect.class);
@Autowired
private SysLogDao sysLogDao;
@Pointcut(“@annotation(com.db.common.anno.RequestLog)”)
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();
//获取目标对象方法
Method targetMethod=targetClass.getDeclaredMethod(
methodName,parameterTypes);
//判定目标方法上是否有RequestLog注解
boolean flag=
targetMethod.isAnnotationPresent(RequestLog.class);
String username=//此工具类需要学完shiro,AOP再进行自定义实现
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);//登陆的用户
//假如目标方法对象上有注解,我们获取注解定义的操作值
if(flag){
RequestLog requestLog=
targetMethod.getDeclaredAnnotation(RequestLog.class);
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.保存日志信息
sysLogDao.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(“HTTPCLIENT_IP”);
}
if (StringUtils._isEmpty
(ip) || “unknown”.equalsIgnoreCase(ip)) {
ip = request.getHeader(“HTTPX_FORWARDED_FOR”);
}
if (StringUtils._isEmpty
(ip) || “unknown”.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
logger.error(“IPUtils ERROR “, e);
}
return ip;
}
}

总结

重点和难点分析

重点:

  1. 日志管理整体架构业务分析及实现。(架构图)

  2. 日志管理持久层映射文件中SQL元素的定义及编写。(重点动态SQL)

  3. 日志管理业务层业务分析,数据封装,异常抛出。

  4. 日志管理控制层请求数据映射,响应数据的封装及转换(转换为json 串)。

  5. 日志管理客户端JS代码,ajax异步请求响应的编写及调试。

难点:

  1. 业务(例如数据业务:持久层,业务层,控制层数据的封装)

  2. JS(方法应用)

2.1) data(key,[value]) :在对象上绑定和获取数据
2.2) removeData([key]): 删除对象上绑定的数据,不写key就是所有.
2.3) prop(attributeName,[attributeValue])获取对象属性值或者赋值
2.4) each(function(){}):迭代元素对象
2.5) ….
2.6) load(url,[params],[callback]):异步加载url
2.7) ajax({options}):jquery中ajax函数
2.8) getJSON(url,[params],[callback])
2.9) post(url,[params],[callback])
2.10)……

问题分析

  1. 如何理解 @Autowired 这个注解?

@Autowired注解用于修饰类中属性,构造方法,set方法,用于告诉spring框架请按照类型(属性类型,构造方法参数类型,set方法参数类型)为属性注入值。假如spring容器提供了多个相同类型对象,此时还可以按照名字(属性名,set方法参数名,构造方法参数名)查找匹配对象。

  1. 如何理解 @Qualifier这个注解?

此注解用于配置@Autowired注解使用,尤其存在多个相同类型对象时,此时可以借助注解指定具体要注入的bean的名字。

  1. 如何理解 Spring MVC中的@RequestParam注解应用?

此注解用于修饰控制层(Handler)对象方法中的参数,用于为参数定义别名(此名字需要与请求url中的参数名相匹配),并且此注解中有一个默认属性required,它的值默认true,表示必须在请求参数中包含这样的一个参数,假如没有则可能会出现400异常。

  1. Spring MVC 中请求处理重点是哪些内容?

  2. 请求路径映射?(传统方式,rest风格)

a)@RequestMapping(value={”doLogListUI”,”doListUI”})
b)@RequestMapping(value=”doDeleteObject/{ids}”)

  1. 请求方式映射?(Get,Post,…)

a)@RequestMapping(value=”..”,method=RequestMethod.GET)
b)@GetMapping(“doFindPageObjects.do”)
c)@PostMapping(“doFindPageObjects.do”)

  1. 请求参数映射?

3.1)直接使用servlet中的api,例如HttpServletRequest
3.2)使用直接量:八种基本类型对应的对象类型+String
3.3)使用Javabean对象:例如SysLog
3.4)…

  1. Spring MVC 响应数据处理?

1)响应数据的封装?(JsonResult,…)
2)响应数据的转换?(例如转json串)
3)响应方式的实现?转发,重定向

原理分析

Mybatis 原理分析(了解)

SqlSessionFactory对象创建分析:

日志管理 - 图13

Dao对象执行分析:

日志管理 - 图14

SqlSession执行分析

日志管理 - 图15

Spring相关原理分析

Spring IOC 初始化分析

日志管理 - 图16

Spring IOC 两大Bean对象
日志管理 - 图17
Spring IOC两大Map对象

日志管理 - 图18

Spring MVC 原理分析:

Spring MVC 核心组件分析:

日志管理 - 图19

Spring MVC 异常处理:

日志管理 - 图20

数据加载设计分析

加载方式设计:
日志管理 - 图21
异步加载过程分析:

日志管理 - 图22

项目FAQ分析

  1. 日志管理分页查询操作实现

1.1 服务端实现

  1. 日志管理分页数据加载的基本过程?(当前页数据获取,总记录数的获取)

  2. 映射文件中元素共性如何提取?(借助sql元素定义共性,使用include进行包含)

  3. 映射文件中参数的获取?(动态sql参数获取时,dao中的参数尽量使用@param修饰)

  4. 持久层只提供了Dao接口没有提供实现,请问实现类是谁创建的?(Spring)

  5. 日志管理中PageObject的作用是什么?(封装当前页面数据以及分页信息)

  6. 日志管理中JsonResult对象的作用是什么?(封装服务端的响应信息)

  7. 日志管理中的异常处理是如何实现的?(定义统一异常处理类,并对异常进行封装)

  8. 进行异常处理的目的是什么?(提高系统的容错能力,改善用户体验)

  9. 服务端控制层将对象转换为json串时使用的json库是什么?(jackson)

  10. Jackson库中的对象将Date对象转换到json串时默认是怎样的存储的?(long)

  11. 假如将对象转换为json串时,希望日期类型的对象按照自己指定格式进行输出,如何实现?(客户端转换,服务端转换)

  12. …..

1.2 客户端实现

  1. 多个异步请求的顺序问题?(两次异步请求需要有一定的先后顺序)

  2. getJSON函数的应用?(Get方式的ajax请求)

  3. JS对象的创建?(原生的,借助jquery)

  4. JS中的循环?(for(var i in records){})

  5. …..

  1. 日志管理删除操作实现?
  1. 服务端

  2. 删除方案?(业务层多次删除,数据层同时删除多个)

  3. 映射文件中动态Sql的编写(foreach)

  4. ….

  1. 客户端

  2. Checkbox值的获取(获取所有checkbox,然后判定选中,最后取选中的值)

  3. 全选操作的实现(业务,change事件处理)

  4. ….

基础加强

  1. 如何理解泛型?

  2. 参数化类型,是JDK1.5的新特性。(定义泛型时使用参数可以简单理解为形参)

  3. 编译时的一种类型,此类型仅仅在编译阶段有效,运行时无效。

  1. 为何使用泛型?

  2. 提高编程时灵活性。

  3. 提高程序运行时的性能。(在编译阶段解决一些运行时需要关注的问题,例如强转)

  1. 泛型的应用类型?

  2. 泛型类: class 类名<泛型>{}

  3. 泛型接口: interface 接口名<泛型>{}

  4. 泛型方法: 访问修饰符 <泛型> 方法返回值类型 方法名(形参){}

  1. 泛型的通配符?(这里的通配符可以看成是一种不确定的类型)

  2. 泛型应用时有一个特殊符号”?”,可以代表一种任意参数类型,注释是实参。

  3. 通配符泛型只能应用于变量的定义。

  1. 泛型的上下界问题?

  2. 指定泛型下界:<? super 类型>

  3. 指定泛型上界:<? extends 类型>

例如:
List<? extends CharSequence> list1=new ArrayList();
List<? super Integer> list2=new ArrayList();

说明:这种上下界一般会用于方法参数变量定义,方法返回值类型定义。

  1. 泛型类型擦除?

泛型是编译时的一种类型,在运行时无效,运行时候都会变成Object类型。

BUG分析

运行时BUG分析套路:(先看错:wwww+h)

  1. When(何时):执行什么业务

  2. What(什么):出现了什么问题?(BindingException,….)

  3. Where(哪里):在哪出现的错?(定位方法,某行,…)

  4. Why(为什么):为什么会有问题呢?

  5. How (如何):如何解决问题?

Bug-01:无法找到对应的Bean对象(NoSuchBeanDefinitionException)

日志管理 - 图23

问题分析

  1. 检测key的名字写的是否正确

  2. 检测spring对此Bean对象的扫描,对于dao而言,查看@MapperScan注解配置

  3. 以上都正确,要检测是否编译了。

Bug-02: 绑定异常(BindingException)

日志管理 - 图24

问题分析:

  1. 接口的类全名与对应的映射文件命名空间不同

  2. 接口的方法名与对应的映射文件元素名不存在

  3. 检测映射文件的路径与SqlSessionFactoryBean中配置的路径是否匹配

  4. 以上都没有问题时,检测你的类和映射文件是否正常编译

Bug-03 反射异常(ReflectionException)

日志管理 - 图25
问题分析:

  1. 映射文件中动态sql中使用的参数在接口方法中没有使用@Param注解修饰

  2. 假如使用了注解修饰还要检测名字是否一致。

说明:当动态sql的参数在接口中没有使用@Param注解修饰,还可以借助_parameter这个变量获取参数的值(mybatis中的一种规范).

Bug-04 参数绑定异常

org.mybatis.spring.MyBatisSystemException: nested exception is
org.apache.ibatis.binding.BindingException: Parameter ‘pageSize’ not found. Available parameters are [startIndex, 2, param3, param1, username, param2]

问题分析:
Dao接口方法定义参数与映射文件SQL语句中使用参数方式不一致

说明:

  1. Dao接口方法使用@Param(“name”)修饰,SQL中则使用 #{name}

  2. Dao 接口方法没有使用@Param修饰,SQL语句中使用#{0},#{1}

或者#{param1},#{param2},..

Bug-05

日志管理 - 图26

问题分析:单元测试项目缺少运行时环境(项目右键选择target runtimes)

Bug-06
日志管理 - 图27

问题分析:getRowCount元素可能没有写resultType或resultMap.

Bug-07
日志管理 - 图28

问题分析:绑定异常,检测findPageObjects方法参数与映射文件参数名字是否匹配.

Bug-08

日志管理 - 图29

Bug-09:

日志管理 - 图30

Bug-10:

日志管理 - 图31

Bug-11

日志管理 - 图32

问题分析:点击右侧VM176:64位置进行查看

Bug-12

日志管理 - 图33