动吧旅游生态系统

项目简介:

这是一个旅游公司内部的管理系统,有部门管理,菜单管理,角色管理,用户管理,日志管理,修改密码
六个模块,基于 MVC 设计思想,进行分层架构设计,其核心目的是将复杂问题简单化,实现各司其职,
各尽所能,然后基于”高内聚,低耦合”的设计思想,再实现各对象之间协同,从而提高系统的可维护
性,可扩展性.

项目技术:SSM、MP、SpringBoot、MySQL、Ajax、JavaScript、Thymleaf

首页初始化

创建呈现首页页面的controller对象PageController

说明:此controller会作为项目中所有页面访问的入口。

BUG分析

请求资源不存在,如下图所示:
动吧旅游生态系统_第1张图片
问题分析:
假如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");
});

dao接口业务描述及设计实现

构建实体对象(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注解进行修饰并定义。

Service接口及实现类

业务描述与设计实现
业务层主要是实现模块中业务逻辑的处理。在日志分页查询中,业务层对象首先要通过业务方法中的参数接收控制层数据(例如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);
	}
}

Controller类实现

业务描述与设计实现
控制层对象主要负责请求和响应数据的处理,例如,本模块首先要通过控制层对象处理请求参数,然后通过业务层对象执行业务逻辑,再通过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);
		 }
	   );
   }

你可能感兴趣的:(javascript,ajax,java,spring,jquery)