动吧项目
权限管理子系统
日志管理
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
日志管理概要设计
原型设计
基于用户需求,实现静态页面,通过静态页面为用户呈现基本需求实现。

API架构设计
日志业务后台应用架构

代码基本结构:(水平分层架构)
数据基本结构(以日志分页查询为例)

日志管理列表页面呈现
服务端实现
Controller实现
业务描述
定义日志管理Controller处理日志管理的客户端请求
修改PageController添加doPageUI方法返回page页面
业务实现
日志管理Controller类的定义
包名 com.db.sys.controller
类名SysLogController
映射 “/log/”
日志管理Controller中方法定义(添加返回页面的相关方法)
方法名 doLogListUI
参数列表(无)
返回值类型:String
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”;
}
客户端实现
首页页面日志管理事件处理
页面描述
准备日志列表页面(WEB-INF/pages/sys/log_list.html)
starter.html页面中点击日志管理菜单时异步加载日志列表页面。
业务实现
事件注册(被点击的元素上)
事件处理函数定义
代码实现:starter.html
$(function(){
$(“#load-log-id”).click(function(){
$(“#mainContentId”).load(“log/doLogListUI.do”);
});
})
load函数为jquery中的ajax异步请求函数。
代码分析:
创建日志列表页面
业务描述
1) 在WEB-INF/pages/sys目录下定义log_list.html页面.
2) 当页面加载完成以后异步加载分页页面(page.html)。
业务实现
在PageController中添加doPageUI方法返回page页面(假如已有则无须再次添加)
在log_list.html页面中异步加载page页面页面
关键代码实现:log_list.html
$(function(){
$(“#pageId”).load(“doPageUI.do”);
});
思考:为什么要先呈现页面,后呈现数据,而不是页面和数据一起呈现。
数据加载通常是一个相对比较耗时操作,为了改善用户体验,可以先为
用户呈现一个页面,数据加载时,显示数据正在加载中。
日志管理列表数据呈现
日志列表数据查询时序图如下:
服务端实现
服务端日志分页查询代码基本架构

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

Entity类实现
业务描述:
定义实体对象(POJO)封装从数据库查询的数据实现ORM映射
业务实现
构建与sys_Logs表对应的实体类型
包名 com.db.sys.entity
类名 SysLog (实现序列化接口,并定义序列化id)
属性 与表(sys_Logs)中字段有对应关系
方法 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;
/**设置:*/<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 /> }**public** Long getTime() {<br /> **return** time;<br /> }**public** **void** setTime(Long time) {<br /> **this**.time = time;<br /> }<br />}
说明:
通过此对象除了可以封装从数据库查询的数据,还可以封装客户端请求数据,实现层与层之间数据的传递。
Dao接口实现
业务描述:(核心-查询当前页显示的数据以及总记录数)
接收业务层参数数据
基于参数进行数据查询
将查询结果进行封装
将结果返回给业务层对象
代码实现:创建接口并定义相关方法。
Dao接口定义
包名: com.db.sys.dao
名字: SysLogDao
方法定义:负责基于条件查询当前页数据
方法名:findPageObjects
参数列表:(String username,Integer startIndex,Integer pageSize)
返回值:List
方法定义:负责基于条件查询总记录数
方法名:getRowCount
参数列表:(String username)
返回值:int
代码实现:
public interface SysLogDao {
/
基于条件分页查询日志信息
@param username 查询条件(例如查询哪个用户的日志信息)
* @param startIndex 当前页的起始位置
* @param pageSize 当前页的页面大小
* @return 当前页的日志记录信息
数据库中每条日志信息封装到一个SysLog对象中
/
List
@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);
}
说明:
当DAO中方法参数多余一个时尽量使用@Param注解进行修饰并指定名字,然后再Mapper文件中便可以通过类似#{username}方式进行获取,否则只能通过#{0},#{1}或者#{param1},#{param2}等方式进行获取。
当DAO方法中的参数应用在动态SQL中时无论多少个参数,尽量使用@Param注解进行修饰并定义。
Mapper文件实现
业务描述
基于Dao接口创建映射文件
基于Dao方法在映射文件中创建映射元素建映射元素
业务实现:
创建映射文件
包名:mapper.sys
文件名:SysLogMapper.xml
命名空间 com.db.sys.dao.SysLogDao
创建映射元素实现翻页查询操作
元素名 select
元素id findPageObjects
参数类型 (不写)
结果类型 com.db.sys.entity.SysLog
SQL定义 select * from sys_Logs where name like ? limit ?,?
创建映射元素实现查询统计操作
元素名 select
元素id getRowCount
参数类型 (不写)
结果类型 int
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 like concat(“%”,#{username},”%”)
思考:
动态sql:基于用于需求动态拼接SQL
Sql标签元素的作用是什么?对sql语句中的共性进行提取,以遍实现更好的复用.
Include标签的作用是什么?引入使用sql标签定义的元素
说明:
当不记得数据库中有哪些函数时可以借助 ? functions 的方式进行查询.其中这个?等价于help单词.
Service接口及实现类
业务描述:核心业务就是分页查询数据并对数据进行封装。
通过参数变量接收控制层数据(例如username,pageCurrent)
对数据进行合法验证(例如pageCurrent不能小于1)
基于参数数据进行总记录数查询(通过此结果要计算总页数)
基于参数数据进行当前页记录的查询(username,startIndex,pageSize)
对数据进行封装(例如封装分页信息和当前页记录)
……….
业务实现:
业务值对象定义:(封装分页信息以及当前数据)
包名 com.db.common.vo (封装值的对象)
类名 PageObject
(实现序列化接口并添加序列化id) 属性 (总行数,总页数,当前页码,页面大小,当前页记录信息)
方法 (set/get)
代码实现:
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 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
}
}
接口定义:为控制层请求提供服务
包名 com.db.sys.service
类名 SysLogService
接口方法定义:
方法名 findPageObjects
参数列表 (String name,Integer pageCurrent)
返回值 PageObject
关键代码实现:
public interface SysLogService {
/
通过此方法实现分页查询操作
@param name 基于条件查询时的参数名
* @param pageCurrent 当前的页码值
* @return* 当前页记录+分页信息
/
PageObject
}
接口实现类的定义:
包名 com.db.sys.service.impl
类名 SysLogServiceImpl
代码实现
@Service
public class SysLogServiceImpl implements SysLogService{
@Autowired
private SysLogDao sysLogDao;
@Override
public PageObject
//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
}
}
说明:在当前方法中需要的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类的实现
业务描述:核心业务是处理客户端请求
接收客户端请求中的数据
基于请求调用业务方法进行请求处理
对处理结果进行封装(JsonResult)
将结果转换为json格式的字符串(适配不同客户端)
将字符串通过服务器输出到客户端。
业务实现:
值对象定义:(封装控制层方法的返回结果)
包名: com.db.common.vo
类名: JsonResult
属性: (状态码,状态信息,正确数据)
方法: (set/get,构造方法)
Controller方法定义:(在SysLogController中定义请求处理方法)
方法名 doFindPageObjects
参数列表 (String name,Integer pageCurrent)
返回值 JsonResult
映射 doFindPageObjects
统一异常处理类的定义
包名 com.db.common.web
类名 GlobalExceptionHandler
注解 @ControllerAdvice
异常类中方法定义(处理参数异常的方法)
方法名 doHandleRuntimeException
参数列表 RuntimeException
返回值 JsonResult
注解描述(@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
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);//封装异常信息
}
}
控制层数据分析

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

客户端实现
客户端数据流转分析

日志信息呈现
业务描述:
列表页面加载完成发起异步请求加载日志信息
通过服务端返回的数据更新当前列表页面
业务实现
定义doGetObjects()函数,通过此函数执行异步加载操作。
分页页面加载完成以后调用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;
}
分页信息呈现
业务描述
列表日志信息异步加载完成以后初始化分页数据(调用setPagination函数)
点击分页元素时异步加载当前页(pageCurrent)数据(调用jumpToPage)
业务实现 (page.html页面中定义JS函数)
定义doSetPagination方法(实现分页数据初始化)
定义doJumpToPage方法(通过此方法实现当前数据查询)
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
}else if(cls==”last”){//最后一页
pageCurrent=pageCount;
}
//3.对pageCurrent值进行重新绑定
$(“#pageId”).data(“pageCurrent”,pageCurrent);
//4.基于新的pageCurrent的值进行当前页数据查询
doGetObjects();
}
列表页面信息查询实现
业务说明
列表查询按钮事件注册
列表查询按钮事件处理函数定义
列表查询参数获取以及传递
业务实现:
在$(function(){})回调函数中追加查询按钮的事件注册。
定义查询按钮的事件处理函数doQueryObjects
重用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);
}
);
}
日志管理删除操作实现
服务端的实现
业务时序图:

Dao接口实现
业务描述
接收业务层参数数据(Integer… ids)
基于数据执行删除操作
业务实现
在SysLogDao接口中定义删除方法
方法名 deleteObjects
方法参数 (Integer… ids)
方法返回值 int
关键代码实现:
int deleteObjects(@Param(“ids”)Integer… ids);
Mapper文件实现
业务描述
基于SysLogDao中方法的定义,编写删除元素。
业务实现:
在SysLogMapper.xml文件中定义删除元素
元素名 delete
元素id deleteObjects
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接口实现
业务描述
接收控制层数据并进行合法验证
基于业务层数据执行删除操作
对删除过程进行监控(先进行异常捕获)
对删除结果进行验证并返回
业务实现
在SysLogService接口及实现类中添加方法
方法名 deleteObjects
参数列表 Integer… ids
返回值 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实现
业务描述
接收客户端请求数据
调用业务层方法执行删除操作
封装结果并返回
业务实现
在SysLogController中定义删除方法
方法名 doDeleteObjects
参数列表 Integer ids
返回值 JsonResult
映射 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
客户端的实现
日志列表页面实现
业务描述:
页面全选操作实现
页面删除按钮事件注册
页面删除操作事件处理函数定义
业务实现:
Thead中全选checkbox元素事件注册及事件处理函数doCheckAll定义
Tbody中checkbox元素事件注册及事件处理函数doChangeCheckAllState定义
在$(function(){})回调函数中追加删除按钮的事件注册操作。
定义事件处理函数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;
}
日志管理数据添加实现
服务端实现
Dao实现
业务描述:
接收业务层数据(SysLog)
将数据写入到数据库(ORM)
业务实现:在SysLogDao接口中定义方法
方法名 insertObject
参数列表 SysLog entity
返回值 int
关键代码实现:
int insertObject(SysLog entity);
Mapper实现
业务描述
基于SysLogDao中方法的定义编写SQL元素。
业务实现:
在SysLogMapper文件中定义insert元素
元素名 insert
元素id insertObject
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;
}
}
总结
重点和难点分析
重点:
日志管理整体架构业务分析及实现。(架构图)
日志管理持久层映射文件中SQL元素的定义及编写。(重点动态SQL)
日志管理业务层业务分析,数据封装,异常抛出。
日志管理控制层请求数据映射,响应数据的封装及转换(转换为json 串)。
日志管理客户端JS代码,ajax异步请求响应的编写及调试。
难点:
业务(例如数据业务:持久层,业务层,控制层数据的封装)
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)……
问题分析
- 如何理解 @Autowired 这个注解?
@Autowired注解用于修饰类中属性,构造方法,set方法,用于告诉spring框架请按照类型(属性类型,构造方法参数类型,set方法参数类型)为属性注入值。假如spring容器提供了多个相同类型对象,此时还可以按照名字(属性名,set方法参数名,构造方法参数名)查找匹配对象。
- 如何理解 @Qualifier这个注解?
此注解用于配置@Autowired注解使用,尤其存在多个相同类型对象时,此时可以借助注解指定具体要注入的bean的名字。
- 如何理解 Spring MVC中的@RequestParam注解应用?
此注解用于修饰控制层(Handler)对象方法中的参数,用于为参数定义别名(此名字需要与请求url中的参数名相匹配),并且此注解中有一个默认属性required,它的值默认true,表示必须在请求参数中包含这样的一个参数,假如没有则可能会出现400异常。
Spring MVC 中请求处理重点是哪些内容?
请求路径映射?(传统方式,rest风格)
a)@RequestMapping(value={”doLogListUI”,”doListUI”})
b)@RequestMapping(value=”doDeleteObject/{ids}”)
- 请求方式映射?(Get,Post,…)
a)@RequestMapping(value=”..”,method=RequestMethod.GET)
b)@GetMapping(“doFindPageObjects.do”)
c)@PostMapping(“doFindPageObjects.do”)
- 请求参数映射?
3.1)直接使用servlet中的api,例如HttpServletRequest
3.2)使用直接量:八种基本类型对应的对象类型+String
3.3)使用Javabean对象:例如SysLog
3.4)…
- Spring MVC 响应数据处理?
1)响应数据的封装?(JsonResult,…)
2)响应数据的转换?(例如转json串)
3)响应方式的实现?转发,重定向
原理分析
Mybatis 原理分析(了解)
SqlSessionFactory对象创建分析:

Dao对象执行分析:

SqlSession执行分析

Spring相关原理分析
Spring IOC 初始化分析

Spring IOC 两大Bean对象
Spring IOC两大Map对象

Spring MVC 原理分析:
Spring MVC 核心组件分析:

Spring MVC 异常处理:

数据加载设计分析
加载方式设计:
异步加载过程分析:

项目FAQ分析
- 日志管理分页查询操作实现
1.1 服务端实现
日志管理分页数据加载的基本过程?(当前页数据获取,总记录数的获取)
映射文件中元素共性如何提取?(借助sql元素定义共性,使用include进行包含)
映射文件中参数的获取?(动态sql参数获取时,dao中的参数尽量使用@param修饰)
持久层只提供了Dao接口没有提供实现,请问实现类是谁创建的?(Spring)
日志管理中PageObject的作用是什么?(封装当前页面数据以及分页信息)
日志管理中JsonResult对象的作用是什么?(封装服务端的响应信息)
日志管理中的异常处理是如何实现的?(定义统一异常处理类,并对异常进行封装)
进行异常处理的目的是什么?(提高系统的容错能力,改善用户体验)
服务端控制层将对象转换为json串时使用的json库是什么?(jackson)
Jackson库中的对象将Date对象转换到json串时默认是怎样的存储的?(long)
假如将对象转换为json串时,希望日期类型的对象按照自己指定格式进行输出,如何实现?(客户端转换,服务端转换)
…..
1.2 客户端实现
多个异步请求的顺序问题?(两次异步请求需要有一定的先后顺序)
getJSON函数的应用?(Get方式的ajax请求)
JS对象的创建?(原生的,借助jquery)
JS中的循环?(for(var i in records){})
…..
- 日志管理删除操作实现?
服务端
删除方案?(业务层多次删除,数据层同时删除多个)
映射文件中动态Sql的编写(foreach)
….
客户端
Checkbox值的获取(获取所有checkbox,然后判定选中,最后取选中的值)
全选操作的实现(业务,change事件处理)
….
基础加强
如何理解泛型?
参数化类型,是JDK1.5的新特性。(定义泛型时使用参数可以简单理解为形参)
编译时的一种类型,此类型仅仅在编译阶段有效,运行时无效。
为何使用泛型?
提高编程时灵活性。
提高程序运行时的性能。(在编译阶段解决一些运行时需要关注的问题,例如强转)
泛型的应用类型?
泛型类: class 类名<泛型>{}
泛型接口: interface 接口名<泛型>{}
泛型方法: 访问修饰符 <泛型> 方法返回值类型 方法名(形参){}
泛型的通配符?(这里的通配符可以看成是一种不确定的类型)
泛型应用时有一个特殊符号”?”,可以代表一种任意参数类型,注释是实参。
通配符泛型只能应用于变量的定义。
泛型的上下界问题?
指定泛型下界:<? super 类型>
指定泛型上界:<? extends 类型>
例如:
List<? extends CharSequence> list1=new ArrayList
List<? super Integer> list2=new ArrayList
说明:这种上下界一般会用于方法参数变量定义,方法返回值类型定义。
- 泛型类型擦除?
泛型是编译时的一种类型,在运行时无效,运行时候都会变成Object类型。
BUG分析
运行时BUG分析套路:(先看错:wwww+h)
When(何时):执行什么业务
What(什么):出现了什么问题?(BindingException,….)
Where(哪里):在哪出现的错?(定位方法,某行,…)
Why(为什么):为什么会有问题呢?
How (如何):如何解决问题?
Bug-01:无法找到对应的Bean对象(NoSuchBeanDefinitionException)

问题分析
检测key的名字写的是否正确
检测spring对此Bean对象的扫描,对于dao而言,查看@MapperScan注解配置
以上都正确,要检测是否编译了。
Bug-02: 绑定异常(BindingException)

问题分析:
接口的类全名与对应的映射文件命名空间不同
接口的方法名与对应的映射文件元素名不存在
检测映射文件的路径与SqlSessionFactoryBean中配置的路径是否匹配
以上都没有问题时,检测你的类和映射文件是否正常编译
Bug-03 反射异常(ReflectionException)

问题分析:
映射文件中动态sql中使用的参数在接口方法中没有使用@Param注解修饰
假如使用了注解修饰还要检测名字是否一致。
说明:当动态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语句中使用参数方式不一致
说明:
Dao接口方法使用@Param(“name”)修饰,SQL中则使用 #{name}
Dao 接口方法没有使用@Param修饰,SQL语句中使用#{0},#{1}
或者#{param1},#{param2},..
Bug-05

问题分析:单元测试项目缺少运行时环境(项目右键选择target runtimes)
Bug-06
问题分析:getRowCount元素可能没有写resultType或resultMap.
Bug-07
问题分析:绑定异常,检测findPageObjects方法参数与映射文件参数名字是否匹配.
Bug-08

Bug-09:

Bug-10:

Bug-11

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

