这是一个旅游公司内部的管理系统,有部门管理,菜单管理,角色管理,用户管理,日志管理,修改密码
六个模块,基于 MVC 设计思想,进行分层架构设计,其核心目的是将复杂问题简单化,实现各司其职,
各尽所能,然后基于”高内聚,低耦合”的设计思想,再实现各对象之间协同,从而提高系统的可维护
性,可扩展性.
说明:此controller会作为项目中所有页面访问的入口。
请求资源不存在,如下图所示:
问题分析:
假如start是模板,检查响应页面是否存在.
检查配置文件中视图的前缀、后缀是否正确
假如start不是html模板,检查对应的Controller方法上是否有@ReponseBody注解
本模块主要是实现对用户行为日志(例如谁在什么时间点执行了什么操作,访问了哪些方法,传递的什么参数,执行时长等)
进行记录、查询、删除等操作。
基于日志管理的请求业务,在PageController中添加doLogUI方法(“log/log_list”),
doPageUI方法分别用于返回日志列表页面,日志分页页面。
首先准备日志列表页面(/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异步请求函数
在log_list.html页面中异步加载page页面,这样可以实现分页页面重用,哪里需要分页页面,
哪里就进行页面加载即可。关键代码如下:
$(function(){
$("#pageId").load("doPageUI");
});
构建实体对象(POJO)封装从数据库查询到的记录,一行记录映射为内存中一个的这样的对象。
对象属性定义时尽量与表中字段有一定的映射关系,并添加对应的set/get/toString等方法,便于对数据进行更好的操作
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);
说明:
当DAO中方法参数多余一个时尽量使用@Param注解进行修饰并指定名字,然后在Mapper文件中便可以通过类似#{username}方式进行获取,否则只能通过#{arg0},#{arg1}或者#{param1},#{param2}等方式进行获取。
当DAO方法中的参数应用在动态SQL中时无论多少个参数,尽量使用@Param注解进行修饰并定义。
业务描述与设计实现
业务层主要是实现模块中业务逻辑的处理。在日志分页查询中,业务层对象首先要通过业务方法中的参数接收控制层数据(例如username,pageCurrent)并校验。然后基于用户名进行总记录数的查询并校验,再基于起始位置及页面大小进行当前页记录的查询,最后对查询结果进行封装并返回。
业务值对象定义,基于此对象封装数据层返回的数据以及计算的分页信息,具体代码参考如下:
/**
* POJO(VO/BO/DTO/DO)中的PO对象:
* 1)所有的POJO对象属性定义都用对象类型
* 2)所有的POJO对象属性默认值无需指定
* 3)所有的POJO对象需要提供set/get方法,toString方法
* 4)所以的POJO对象需要提供无参构造
* 5)所有的POJO对象的构造方法不要写任何业务逻辑
*
* 此对象为业务层向外输出的一个BO对象,用于封装业务执行的结果
* @author PC
*
* @param
* 类上定义的泛型用于约束类中的属性,方法参数,方法返回值类型.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageObject<T> implements Serializable{
private static final long serialVersionUID = 5727659641634783999L;
/**当前记录数*/
private List<T> records;
/**总行数(通过查询获得)*/
private Integer rowCount;
/**总页数*/
private Integer pageCount;
/**每页要显示多少条记录*/
private Integer pageSize;
/**当前页的页码值*/
private Integer pageCurrent;
public PageObject(List<T> records, Integer rowCount, Integer pageSize, Integer pageCurrent) {
super();
this.records = records;
this.rowCount = rowCount;
this.pageSize = pageSize;
this.pageCurrent = pageCurrent;
//计算分页:方案一
// this.pageCount=rowCount/pageSize;
// if (rowCount%pageSize!=0) {
// pageCount++;
// }
//计算分页:方案二
this.pageCount=(rowCount-1)/pageSize+1;
}
}
定义日志业务接口及方法,暴露外界对日志业务数据的访问,其代码参考如下:
package com.cy.pj.sys.service;
public interface SysLogService {
/**
* @param name 基于条件查询时的参数名
* @param pageCurrent 当前的页码值
* @return 当前页记录+分页信息
*/
PageObject<SysLog> findPageObjects(
String username,
Integer pageCurrent);
}
日志业务接口及实现类,用于具体执行日志业务数据的分页查询操作,其代码如下:
@Override
public PageObject<SysLog> findPageObjects(String username, Integer pageCurrent) {
//1.参数校验(思考username允许为空?允许)
//请问如下参数校验是否可以颠倒"||"两侧的顺序?(不可以!)
if (pageCurrent==null||pageCurrent<1)
throw new IllegalArgumentException("当前页码值不合法");
//2.查询总记录数,并进行校验
//假如在如下行出现空指针,可能问题的原因是什么?sysLogDao变量为空
int rowCount = sysLogDao.getRowCount(username);
if (rowCount==0)
throw new ServiceException("没有对应记录");//此异常如何定义?
//3.查询当前页记录
//定义每页最多要显示的记录数
int pageSize=15;
//计算当前页查询的起始位置
int startIndex=(pageCurrent-1)*pageSize;
List<SysLog> records =
sysLogDao.findPageObjects(username, startIndex, pageSize);
//4.对业务层查询结果进行处理和封装
return new PageObject<>(records, rowCount, pageSize, pageCurrent);
}
在当前方法中需要的ServiceException是一个自己定义的异常, 通过自定义异常可更好的实现对业务问题的描述,
同时可以更好的提高用户体验。参考代码如下:
package com.cy.pj.common.exception;
/**
* 自定义业务异常
* @author PC
*
*/
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = -9085326160255400760L;
public ServiceException() {
super();
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
public ServiceException(String message) {
super(message);
}
public ServiceException(Throwable cause) {
super(cause);
}
}
业务描述与设计实现
控制层对象主要负责请求和响应数据的处理,例如,本模块首先要通过控制层对象处理请求参数,然后通过业务层对象执行业务逻辑,再通过VO对象封装响应结果(主要对业务层数据添加状态信息),最后将响应结果转换为JSON格式的字符串响应到客户端。
关键代码设计与实现
定义控制层值对象(VO),目的是基于此对象封装控制层响应结果(在此对象中主要是为业务层执行结果添加状态信息)。Spring MVC框架在响应时可以调用相关API(例如jackson)将其对象转换为JSON格式字符串。
package com.cy.pj.common.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* VO:(View Object/Value Object),在当前项目中借助VO封装视图层要呈现的数据
*
*/
@Data
@NoArgsConstructor
public class JsonResult {
/**消息状态码*/
private Integer state;//1表示正确数据,0表示异常数据
/**状态码对应的具体信息*/
private String message;
/**数据(基于此属性封装业务层返回的数据)*/
private Object data;
public JsonResult(String message){
this.state=1;
this.message=message;
//方法2
//setState(1);
//setMessage(message);
}
public JsonResult(Object data){
this.state=1;
this.data=data;
}
//基于此方法进行错误信息的初始化
public JsonResult(Throwable e) {//Throwable是所有异常类的父类
this.state=0;//error
this.message=e.getMessage();//获取异常信息
}
}
在SysLogController类中添加分页请求处理方法,代码参考如下:
@RequestMapping("doFindPageObjects")
@ResponseBody
public JsonResult doFindPageObjects(String username,Integer pageCurrent){
PageObject<SysLog> pageObject=
sysLogService.findPageObjects(username,pageCurrent);
return new JsonResult(pageObject);
}
业务描述与设计实现
日志分页页面加载完成以后,向服务端发起异步请求加载日志信息,当日志信息加载完成需要将日志信息、分页信息呈现到列表页面上。
关键代码设计与实现
第一步:分页页面加载完成,向服务端发起异步请求,代码参考如下:
$(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函数
}
第三步:定义回调函数,处理服务端的响应结果。代码如下:
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元素,并填充具体业务数据。代码参考如下:
function doCreateTds(data){
var tds=" "+
""+data.username+" "+
""+data.operation+" "+
""+data.method+" "+
""+data.params+" "+
""+data.ip+" "+
""+data.time+" ";
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<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对象?对象
doHandleResponseResult(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);
}
);
}