精华内容
下载资源
问答
  • 在撰写本文时, /foreign-travel-advice/*仍使用此路由。 /email/subscriptions/new路线[ ]。 此路由使任何其他应用程序都可以提供完全不依赖于内容存储库的完全自定义的电子邮件注册体验。 它由类的应用程序使用...
  • 一起Travel吧!(菜单模块) 1.1 业务设计说明 菜单管理又称为资源管理,是系统资源对外的表现形式。本模块主要是实现对菜单进行添加、修改、查询、删除等操作。其表设计语句如下: CREATE TABLE `sys_menus` ( `id` ...

    一起Travel吧! (菜单模块)

    1.1 业务设计说明

    菜单管理又称为资源管理,是系统资源对外的表现形式。本模块主要是实现对菜单进行添加、修改、查询、删除等操作。其表设计语句如下:

    CREATE TABLE `sys_menus` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(50) DEFAULT NULL COMMENT '资源名称',
      `url` varchar(200) DEFAULT NULL COMMENT '资源URL',
      `type` int(11) DEFAULT NULL COMMENT '类型     1:菜单   2:按钮',
      `sort` int(11) DEFAULT NULL COMMENT '排序',
      `note` varchar(100) DEFAULT NULL COMMENT '备注',
      `parentId` int(11) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
      `permission` varchar(500) DEFAULT NULL COMMENT '授权(如:sys:user:create)',
      `createdTime` datetime DEFAULT NULL COMMENT '创建时间',
      `modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
      `createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',
      `modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='资源管理';
    
    

    菜单表与角色表是多对多的关系,在表设计时,多对多关系通常由中间表(关系表)进行维护,如图-1所示:
    在这里插入图片描述
    基于角色菜单表的设计,其角色和菜单对应的关系数据要存储到关系表中,其具体存储形式,如图-2所示:
    在这里插入图片描述
    菜单与角色的关系表的SQL设计如下:

    CREATE TABLE `sys_role_menus` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `role_id` int(11) DEFAULT NULL COMMENT '角色ID',
      `menu_id` int(11) DEFAULT NULL COMMENT 'ID',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';
    

    1.2 原型设计说明

    基于用户需求,实现菜单静态页面(html/css/js),通过静态页面为用户呈现菜单模块的基本需求实现。
    当在主页左侧菜单栏,点击菜单管理时,在主页内容呈现区,呈现菜单列表页面,如
    图-3所示。
    在这里插入图片描述
    当在菜单列表页面点击添加按钮时,异步加载菜单编辑页面,并在列表内容呈现区,呈现菜单编辑页面,如图-4所示。
    在这里插入图片描述
    在菜单编辑页面选择上级菜单时,异步加载菜单信息,并以树结构的形式呈现上级菜单,如图-5所示。
    在这里插入图片描述
    说明:假如客户对此原型进行了确认,后续则可以基于此原型进行研发。

    1.3 API设计说明

    菜单管理业务后台API分层架构及调用关系如图-6所示:
    在这里插入图片描述
    说明:分层目的主要将复杂问题简单化,实现各司其职,各尽所能。

    2 菜单管理列表页面呈现

    2.1 业务时序分析

    菜单管理页面的加载过程,其时序分析如图-7所示:
    在这里插入图片描述
    2.2 服务端实现

    2.2.1 Controller实现

    Controller实现

    业务描述与设计实现
    基于菜单管理的请求业务,在PageController中添加doMenuUI方法,用于返回菜单列表页面。

    关键代码设计与实现
    第一步:在PageController中定义返回菜单列表的方法。代码如下:

    @RequestMapping("menu/menu_list")
    public String doMenuUI() {
    	return "sys/menu_list";
    }
    

    第二步:在PageController中基于rest风格的url方式优化返回UI页面的方法。找出共性进行提取,例如:

    @RequestMapping("{module}/{moduleUI}")
    public String doModuleUI(@PathVariable String moduleUI) {
    		return "sys/"+moduleUI;
    }
    
    

    2.3 客户端实现

    2.3.1 首页菜单事件处理

    首页菜单事件处理

    业务描述与设计实现
    首先准备菜单列表页面(/templates/pages/sys/menu_list.html),然后在starter.html页面中点击菜单管理时异步加载菜单列表页面。

    关键代码设计与实现
    找到项目中的starter.html页面,页面加载完成以后,注册菜单管理项的点击事件,当点击菜单管理时,执行事件处理函数。关键代码如下:

    $(function(){doLoadUI("load-menu-id","menu/menu_list")
    })
    
    

    说明:对于doLoadUI函数,假如在starter.html中已经定义,则无需再次定义.

    function doLoadUI(id,url){
     	$("#"+id).click(function(){
        		$("#mainContentId").load(url);
       });
    }
    
    

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

    2.3.2 菜单列表页面

    业务描述与设计实现

    本页面呈现菜单信息时要以树结构形式进行呈现。此树结构会借助jquery中的treeGrid插件进行实现,所以在菜单列表页面需要引入treeGrid相关JS。但是,具体的treeGrid怎么用可自行在网上进行查询(已比较成熟)。

    关键代码设计与实现:
    关键JS引入(menu_list.html),代码如下:

    <script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.extension.js"></script>
    <script type="text/javascript" src="bowe
    r_components/treegrid/jquery.treegrid.min.js"></script>
    <script type="text/javascript" src="bower_components/treegrid/tree.table.js"></script>
    
    

    3.菜单管理列表数据呈现

    3.1 数据架构分析

    菜单列表页面加载完成,启动菜单数据异步加载操作,本次菜单列表页面要呈现菜单以及上级菜单信息,其数据查询时,数据的封装及传递过程,如图-8所示。
    在这里插入图片描述
    说明:本模块将从数据库查询到的菜单数据封装到map对象,一行记录一个map对象,其中key为表中的字段(列)名,值为字段(列)对应的值。
    数据加载过程其时序分析,如图-9所示:
    在这里插入图片描述
    3.2 服务端关键业务及代码实现

    3.2.1 Dao接口实现

    业务描述及设计实现
    通过数据层对象,基于业务层参数,查询菜单以及上级菜单信息(要查询上级菜单名)。

    关键代码分析及实现
    第一步:定义数据层接口对象,通过此对象实现数据库中菜单数据的访问操作。关键代码如下:

    @Mapper
    public interface SysMenuDao {
    }
    
    

    第二步:在SysMenuDao接口中添加findObjects方法,基于此方法实现菜单数据的查询操作。代码如下:

    List<Map<String,Object>> findObjects();
    
    

    说明:一行记录映射为一个map对象,多行存储到list。
    思考:这里为什么使用map存储数据,有什么优势劣势?
    存取速度相对快;线程不安全

    3.2.2 Mapper文件实现

    业务描述及设计实现
    基于Dao接口创建映射文件,在此文件中通过相关元素(例如select)描述要执行的数据操作。

    关键代码设计及实现
    第一步:在映射文件的设计目录中添加SysMenuMapper.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.SysMenuDao">
      
    </mapper>
    
    

    第二步:在映射文件中添加id为findObjects的元素,实现菜单记录查询。我们要查询所有菜单以及菜单对应的上级菜单名称。关键代码如下:

    <select id="findObjects" resultType="map">
             
              <!-- 方案1
              select c.*,p.name parentName
              from sys_menus c left join sys_menus p
              on c.parentId=p.id 
              -->
              <!-- 方案2 -->
              select c.*,(
                        select p.name 
                        from sys_menus p
                        where c.parentId=p.id
                        ) parentName
              from sys_menus c
             
     </select>
    
    

    说明:自关联查询分析,如图-10所示:
    在这里插入图片描述
    3.2.3 Service接口及实现类

    Service接口及实现类

    业务描述与设计实现
    在菜单查询中,业务层对象主要是借助数据层对象完成菜单数据的查询。后续还可以基于AOP对数据进行缓存,记录访问日志等。

    关键代码设计及实现
    第一步:定义菜单业务接口及方法,暴露外界对菜单业务数据的访问,其代码参考如下:

    package com.cy.pj.sys.service;
    public interface SysMenuService {
    	 List<Map<String,Object>> findObjects();
    }
    
    

    第二步:定义菜单业务接口实现类,并添加菜单业务数据对应的查询操作实现,其代码参考如下:

    package com.cy.pj.sys.service.impl;
    @Service
    public class SysMenuServiceImpl implements SysMenuService{
    	  @Autowired
          private SysMenuDao sysMenuDao;
    	  @Override
    	  public List<Map<String, Object>> findObjects() {
    		List<Map<String,Object>> list=
    			sysMenuDao.findObjects();
    		if(list==null||list.size()==0)
    		throw new ServiceException("没有对应的菜单信息");
    		return list;
    }
    
    

    3.2.4 Controller类实现

    业务描述与设计实现
    控制层对象主要负责请求和响应数据的处理,例如,本模块通过业务层对象执行业务逻辑,再通过VO对象封装响应结果(主要对业务层数据添加状态信息),最后将响应结果转换为JSON格式的字符串响应到客户端。

    关键代码设计与实现
    定义Controller类,并将此类对象使用Spring框架中的@Controller注解进行标识,表示此类对象要交给Spring管理。然后基于@RequestMapping注解为此类定义根路径映射。代码参考如下:

    package com.cy.pj.sys.controller;
    @RequestMapping("/menu/")
    @RestController
    public class SysMenuController {
    }
    
    

    说明:这里的@RestController注解等效于在类上同时添加了@Controller和 @ResponseBody注解.
    在Controller类中添加菜单查询处理方法,代码参考如下:

    @RequestMapping("doFindObjects")
    public JsonResult doFindObjects() {
    	return new  JsonResult(sysMenuService.findObjects());
    }
    
    

    3.3 客户端关键业务及代码实现

    3.3.1 菜单列表信息呈现

    业务描述与设计实现
    菜单页面加载完成以后,向服务端发起异步请求加载菜单信息,当菜单信息加载完成需要将菜单信息呈现到列表页面上。

    关键代码设计与实现
    第一步:在菜单列表页面引入treeGrid插件相关的JS。

    <script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.extension.js"></script>
    <script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.min.js"></script>
    <script type="text/javascript" src="bower_components/treegrid/tree.table.js"></script>
    
    

    第二步:在菜单列表页面,定义菜单列表配置信息,关键代码如下:

    var columns = [
    {
    	field : 'selectItem',
    	radio : true
    },
    {
    	title : '菜单ID',
    	field : 'id',
    	align : 'center',
    	valign : 'middle',
    	width : '80px'
    },
    {
    	title : '菜单名称',
    	field : 'name',
    	align : 'center',
    	valign : 'middle',
    	width : '130px'
    },
    {
    	title : '上级菜单',
    	field : 'parentName',
    	align : 'center',
    	valign : 'middle',
    	sortable : true,
    	width : '100px'
    },
    {
    	title : '类型',
    	field : 'type',
    	align : 'center',
    	valign : 'middle',
    	width : '70px',
    	formatter : function(item, index) {
    		if (item.type == 1) {
    			return '<span class="label label-success">菜单</span>';
    		}
    		if (item.type == 2) {
    			return '<span class="label label-warning">按钮</span>';
    		}
    	}
    }, 
    {
    	title : '排序号',
    	field : 'sort',
    	align : 'center',
    	valign : 'middle',
    	sortable : true,
    	width : '70px'
    }, 
    {
    	title : '菜单URL',
    	field : 'url',
    	align : 'center',
    	valign : 'middle',
     
    	width : '160px'
    }, 
    {
    	title : '授权标识',//要显示的标题名称
    	field : 'permission',//json串中的key
    	align : 'center',//水平居中
    	valign : 'middle',//垂直居中
    	sortable : false //是否排序
    } ];//格式来自官方demos -->treeGrid(jquery扩展的一个网格树插件)
    
    

    第三步:定义异步请求处理函数,代码参考如下:

    function doGetObjects(){//treeGrid
    	//1.构建table对象(bootstrap框架中treeGrid插件提供)
    	var treeTable=new TreeTable(
    			"menuTable",//tableId
    			"menu/doFindObjects",//url
    			 columns);
    	//设置从哪一列开始展开(默认是第一列)
    	//treeTable.setExpandColumn(2);
    	//2.初始化table对象(底层发送ajax请求获取数据)
    	treeTable.init();//getJSON,get(),...
    }
    
    

    第四步:页面加载完成,调用菜单查询对应的异步请求处理函数,关键代码如下:

    $(function(){
    	doGetObjects();
    })
    
    

    4.菜单管理删除操作实现

    4.1 业务时序分析

    基于用户在列表页面上选择的的菜单记录ID,执行删除操作,本次删除业务实现中,首先要基于id判断当前菜单是否有子菜单,假如有子菜单则不允许删除,没有则先删除菜单角色关系数据,然后再删除菜单自身信息。其时序分析如图-11所示:
    在这里插入图片描述
    4.2 服务端关键业务及代码实现

    4.2.1 Dao接口实现

    业务描述及设计实现
    数据层基于业务层提交的菜单记录id,删除菜单角色关系以及菜单数据,菜单自身记录信息。

    关键代码设计及实现
    第一步:创建SysRoleMenuDao并定义基于菜单id删除关系数据的方法,关键代码如下:

    @Mapper
    public interface SysRoleMenuDao {
    	int deleteObjectsByMenuId(Integer menuId);
    }
    
    

    第二步:在SysMenuDao中添加基于菜单id查询子菜单记录的方法。代码参考如下:

    int getChildCount(Integer id);
    
    

    第三步:在SysMenuDao中添加基于菜单id删除菜单记录的方法。代码参考如下:

    int deleteObject(Integer id);
    
    

    4.2.2 Mapper文件实现

    业务描述及设计实现
    在SysRoleMenuDao,SysMenuDao接口对应的映射文件中添加用于执行删除业务的delete元素,然后在元素内部定义具体的SQL实现。

    关键代码设计与实现
    第一步:创建SysRoleMenuMapper.xml文件并添加基于菜单id删除关系数据的元素,关键代码如下:

    <?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.SysRoleMenuDao">
    	<delete id="deleteObjectsByMenuId"
                 parameterType="int">
              delete from sys_role_menus
              where menu_id=#{menuId}
    	</delete>
    </mapper>
    
    

    第二步:在SysMenuMapper.xml文件中添加基于id统计子菜单数量的元素,关键代码如下:

    <select id="getChildCount"
                 parameterType="int"
                 resultType="int">
              select count(*)
              from sys_menus
              where parentId=#{id}        
     </select>
    
    

    第三步:在SysMenuMapper.xml文件添加delete元素,基于带单id删除菜单自身记录信息,关键代码如下:

     <delete id="deleteObject">
           delete from sys_menus
           where id =#{id}
     </delete>
    
    

    4.2.3 Service接口及实现类

    业务描述与设计实现
    在菜单业务层定义用于执行菜单删除业务的方法,首先通过方法参数接收控制层传递的菜单id,并对参数id进行校验。然后基于菜单id统计子菜单个数,假如有子菜单则抛出异常,提示不允许删除。假如没有子菜单,则先删除角色菜单关系数据。最后删除菜单自身记录信息后并返回业务执行结果。

    关键代码设计与实现
    第一步:在SysMenuService接口中,添加基于id进行菜单删除的方法。关键代码如下:

    int deleteObject(Integer id);
    
    

    第二步:在SysMenuServiceImpl实现类中注入SysRoleMenuDao相关对象。关键代码如下:

    @Autowired
    private SysRoleMenuDao sysRoleMenuDao;
    
    

    第三步:在SysMenuServiceImpl实现类中添加删除业务的具体实现。关键代码如下:

    @Override
    	public int deleteObject(Integer id) {
    		//1.验证数据的合法性
    		if(id==null||id<=0)
    		throw new IllegalArgumentException("请先选择");
    		//2.基于id进行子元素查询
    		int count=sysMenuDao.getChildCount(id);
    		if(count>0)
    		throw new ServiceException("请先删除子菜单");
    		//3.删除角色,菜单关系数据
    		sysRoleMenuDao.deleteObjectsByMenuId(id);
    		//4.删除菜单元素
    		int rows=sysMenuDao.deleteObject(id);
    		if(rows==0)
    		throw new ServiceException("此菜单可能已经不存在");
    		//5.返回结果
    		return rows;
    	}
    
    

    4.2.4 Controller类实现

    业务描述与设计实现
    在菜单控制层对象中,添加用于处理菜单删除请求的方法。首先在此方法中通过形参接收客户端提交的数据,然后调用业务层对象执行删除操作,最后封装执行结果,并在运行时将响应对象转换为JSON格式的字符串,响应到客户端。

    关键代码设计与实现
    第一步:在SysMenuController中添加用于执行删除业务的方法。代码如下:

    @RequestMapping("doDeleteObject")
    	  public JsonResult doDeleteObject(Integer id){
    		  sysMenuService.deleteObject(id);
    		  return new JsonResult("delete ok");
    	  }
    
    

    第二步:启动tomcat进行访问测试,打开浏览器输入如下网址:
    访问测试

    4.3 客户端关键业务及代码实现

    4.3.1 菜单列表页面事件处理

    业务描述及设计实现
    用户在页面上首先选择要删除的元素,然后点击删除按钮,将用户选择的记录id异步提交到服务端,最后在服务端执行菜单的删除动作。

    关键代码设计与实现
    第一步:页面加载完成以后,在删除按钮上进行点击事件注册。关键代码如下:

    ...
    $(".input-group-btn")
    	   .on("click",".btn-delete",doDeleteObject)
    ...
    
    

    第二步:定义删除操作对应的事件处理函数。关键代码如下:

    function doDeleteObject(){
    	//1.获取选中的记录id
    	var id=doGetCheckedId();
    	if(!id){
    	  alert("请先选择");
    	  return;
    	}
       //2.给出提示是否确认删除
      if(!confirm("确认删除吗"))return;
    	//3.异步提交请求删除数据
    	var url="menu/doDeleteObject";
    	var params={"id":id};
    	$.post(url,params,function(result){
    		if(result.state==1){
    			alert(result.message);
    			$("tbody input[type='radio']:checked")
    	          .parents("tr").remove();
    		}else{
    			alert(result.message);
    		}
    	});
    }
    
    

    第三步:定义获取用户选中的记录id的函数。关键代码如下:

    function doGetCheckedId(){
    	//1.获取选中的记录
    	var selections=$("#menuTable")
    	//bootstrapTreeTable是treeGrid插件内部定义的jquery扩展函数
    	//getSelections为扩展函数内部要调用的一个方法
    	.bootstrapTreeTable("getSelections");
    	//2.对记录进行判定
    	if(selections.length==1)
    	return selections[0].id;
    }
    
    

    5.菜单添加页面呈现

    5.1 业务时序分析

    添加页面加载时序分析,如图-12所示:
    在这里插入图片描述
    5.2 准备菜单编辑页面

    首先准备菜单列表页面(/templates/pages/sys/menu_edit.html),然后在menu_list.html页面中点击菜单添加时异步加载菜单编辑页面。

    5.3 菜单编辑页面呈现

    业务描述与设计实现
    菜单列表页面点击添加按钮时,异步加载菜单编辑页面。

    关键代码设计与实现
    第一步:菜单列表页面上,对添加按钮进行事件注册,关键代码如下:

    $(document).ready(function(){
        ...	
    	$(".input-group-btn")
        .on("click",".btn-add",doLoadEditUI);
    });
    第二步:定义添加按钮事件处理函数,关键代码如下:
    function doLoadEditUI(){
    	var title;
    	if($(this).hasClass("btn-add")){
    		title="添加菜单"
    	}
    	var url="menu/menu_edit";
    	$("#mainContentId").load(url,function(){
    		$(".box-title").html(title);
    	})
    }
    
    

    6.菜单编辑页面上级菜单呈现

    6.1 业务时序分析

    在菜单编辑页面上,点击上级菜单时,其数据加载时序分析,如图-13所示:
    在这里插入图片描述
    6.2 服务端关键业务及代码实现

    6.2.1 Node对象

    业务描述与设计实现
    定义值对象封装查询到的上级菜单id,name,parentId信息。

    关键代码设计与实现

    package com.cy.pj.common.vo;
    public class Node implements Serializable{
    	private static final long serialVersionUID = -6577397050669133046L;
     
    	private Integer id;
    	private String name;
    	private Integer parentId;
    	
    	public Integer getId() {
    		return id;
    	}
    	public void setId(Integer id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public Integer getParentId() {
    		return parentId;
    	}
    	public void setParentId(Integer parentId) {
    		this.parentId = parentId;
    	}
    	@Override
    	public String toString() {
    		return "Node [id=" + id + ", name=" + name + ", parentId=" + parentId + "]";
    	}
    	
    }
    
    

    6.2.2 Dao接口实现

    Dao接口实现

    业务描述与设计实现
    基于请求获取数据库对应的菜单表中的所有菜单id,name,parentId,一行记录封装为一个Node对象,多个node对象存储到List集合

    关键代码设计与实现
    在SysMenuDao接口中添加,用于查询上级菜单相关信息。关键代码如下:

    List<Node> findZtreeMenuNodes();
    
    

    6.2.3 Mapper映射文件

    业务描述与设计实现
    基于SysMenuMapper中方法的定义,编写用于菜单查询的SQL元素。

    关键代码设计与实现
    在SysMenuMapper.xml中添加findZtreeMenuNodes元素,用于查询上级菜单信息。关键代码如下:

    <select id="findZtreeMenuNodes"
                resultType="com.cy.pj.common.vo.Node">
            select id,name,parentId
            from sys_menus        
        </select>
    
    

    6.2.4 Service接口及实现类

    业务描述与设计实现
    基于用户请求,通过数据层对象获取上级菜单相关信息。

    关键代码实现
    第一步:在SysMenuService接口中,添加查询菜单信息的方法。关键代码如下:

    List<Node> findZtreeMenuNodes()
    
    

    第二步:在SysMenuServiceImpl类中添加,查询菜单信息方法的实现。关键代码如下:

    @Override
    	public List<Node> findZtreeMenuNodes() {
    		return sysMenuDao.findZtreeMenuNodes();
    	}
    
    

    6.2.5 Controller类实现

    业务描述与设计实现
    基于客户端请求,访问业务层对象方法,获取菜单节点对象,并封装返回。

    关键代码设计与实现

    @RequestMapping("doFindZtreeMenuNodes")
    public JsonResult doFindZtreeMenuNodes(){
    	return new JsonResult(
    	sysMenuService.findZtreeMenuNodes());
    }
    
    

    6.3 客户端关键业务及代码实现

    6.3.1 ZTree结构定义

    业务描述与设计实现
    本模块以开源JS组件方式实现ZTree结构信息的呈现。

    关键代码设计与实现
    在menu_edit.html页面中定义用于呈现树结构的DIV组件(假如已有则无需定义)

    <div class="layui-layer layui-layer-page layui-layer-molv layer-anim" id="menuLayer" type="page" times="2" showtime="0" contype="object"
    		style="z-index:59891016; width: 300px; height: 450px; top: 100px; left: 500px; display:none">
    		<div class="layui-layer-title" style="cursor: move;">选择菜单</div>
    		<div class="layui-layer-content" style="height: 358px;">
    			<div style="padding: 10px;" class="layui-layer-wrap">
    				<ul id="menuTree" class="ztree"></ul>    <!-- 动态加载树 -->
    			</div>
    		</div>
    		<span class="layui-layer-setwin"> <a class="layui-layer-ico layui-layer-close layui-layer-close1 btn-cancel" ></a></span>
    		<div class="layui-layer-btn layui-layer-btn-">
    			<a class="layui-layer-btn0 btn-confirm">确定</a>
    			<a class="layui-layer-btn1 btn-cancel">取消</a>
    	     </div>
          </div>
    
    

    6.3.2 ZTree数据呈现

    ZTree数据呈现

    业务描述与设计实现
    引入zTree需要的JS,并,并基于JS中的定义的API初始化zTree中的菜单信息。

    关键代码设计与实现
    第一步:引入js文件

    <script type="text/javascript" src="bower_components/ztree/jquery.ztree.all.min.js"></script>
      <script type="text/javascript" src="bower_components/layer/layer.js">
      </script>
    
    

    第二步:在menu_edit.html中定义zTree配置信息(初始化zTree时使用)

    var zTree; 
    var setting = {
      	data : {
      		simpleData : {
      			enable : true,
      			idKey : "id",  //节点数据中保存唯一标识的属性名称
      			pIdKey : "parentId",  //节点数据中保存其父节点唯一标识的属性名称
      			rootPId : null  //根节点id
      		}
      	}
      }
    
    

    第三步:定义异步加载zTree信息的函数,关键代码如下:

     function doLoadZtreeNodes(){
    	  
    	  var url="menu/doFindZtreeMenuNodes";
    	  //异步加载数据,并初始化数据
    	  $.getJSON(url,function(result){
    		  if(result.state==1){
    			  //使用init函数需要先引入ztree对应的js文件
    			  zTree=$.fn.zTree.init(
    					  $("#menuTree"),
    					  setting,
    					  result.data);
    	           $("#menuLayer").css("display","block");
    		  }else{
    			  alert(result.message);
    		  }
    	  })
      }
    
    

    第四步:定义zTree中取消按钮事件处理函数,点击取消隐藏zTree。关键代码如下:

      function doHideTree(){
    	 $("#menuLayer").css("display","none");
      }
    

    第五步:定义zTree中确定按钮对应的事件处理处理函数。关键代码如下:

    function doSetSelectNode(){
    	  //1.获取选中的节点对象
    	  var nodes=zTree.getSelectedNodes();
    	  if(nodes.length==1){	  
    	  var node=nodes[0];
    	  console.log(node);
    	  //2.将对象中内容,填充到表单
    	  $("#parentId").data("parentId",node.id);
    	  $("#parentId").val(node.name);
    }
    	  //3.隐藏树对象
    	  doHideTree();
      }
    
    

    第六步:定义页面加载完成以后的事件处理函数:

     $(document).ready(function(){
    	  $("#mainContentId")
    	  .on("click",".load-sys-menu",doLoadZtreeNodes)
          $("#menuLayer")
          .on("click",".btn-confirm",doSetSelectNode)
          .on("click",".btn-cancel",doHideTree)
      });
    
    

    7.菜单数据添加实现

    7.1 数据基本架构分析

    用户在菜单编辑页面输入数据,然后异步提交到服务端,其简易数据传递基本架构,如图-14所示:

    在这里插入图片描述
    用户在菜单添加页面中填写好菜单数据,然后点击保存按钮,将用户填写的数据添加
    到数据库。其时序分析,如图-15所示:
    在这里插入图片描述
    7.2 服务端关键业务及代码实现

    7.2.1 Entity类定义

    Entity类定义

    业务描述与设计实现
    定义持久化对象,封装客户端请求数据,并将数据传递到数据层进行持久化。

    关键代码设计与实现
    菜单持久层对象类型定义,关键代码如下:

    public class SysMenu implements Serializable{
    	private static final long serialVersionUID = -8805983256624854549L;
    	private Integer id;
    	/**菜单名称*/
    	private String name;
    	/**菜单url: log/doFindPageObjects*/
    	private String url;
    	/**菜单类型(两种:按钮,普通菜单)*/
    	private Integer type=1;
    	/**排序(序号)*/
    	private Integer sort;
    	/**备注*/
    	private String note;
    	/**上级菜单id*/
    	private Integer parentId;
    	/**菜单对应的权限标识(sys:log:delete)*/
    	private String permission;
    	/**创建用户*/
    	private String createdUser;
    	/**修改用户*/
    	private String modifiedUser;
    	private Date createdTime;
    	private Date modifiedTime;
    	
    	public Integer getId() {
    		return id;
    	}
    	public void setId(Integer id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public String getUrl() {
    		return url;
    	}
    	public void setUrl(String url) {
    		this.url = url;
    	}
    	public Integer getType() {
    		return type;
    	}
    	public void setType(Integer type) {
    		this.type = type;
    	}
    	public Integer getSort() {
    		return sort;
    	}
    	public void setSort(Integer sort) {
    		this.sort = sort;
    	}
    	public String getNote() {
    		return note;
    	}
    	public void setNote(String note) {
    		this.note = note;
    	}
    	public Integer getParentId() {
    		return parentId;
    	}
    	public void setParentId(Integer parentId) {
    		this.parentId = parentId;
    	}
    	public String getPermission() {
    		return permission;
    	}
    	public void setPermission(String permission) {
    		this.permission = permission;
    	}
    	public String getCreatedUser() {
    		return createdUser;
    	}
    	public void setCreatedUser(String createdUser) {
    		this.createdUser = createdUser;
    	}
    	public String getModifiedUser() {
    		return modifiedUser;
    	}
    	public void setModifiedUser(String modifiedUser) {
    		this.modifiedUser = modifiedUser;
    	}
    	public Date getCreatedTime() {
    		return createdTime;
    	}
    	public void setCreatedTime(Date createdTime) {
    		this.createdTime = createdTime;
    	}
    	public Date getModifiedTime() {
    		return modifiedTime;
    	}
    	public void setModifiedTime(Date modifiedTime) {
    		this.modifiedTime = modifiedTime;
    	}
    }
    
    

    7.2.2 DAO接口定义

    业务描述与设计实现
    负责将用户提交的菜单数据,持久化到数据库。

    关键代码设计与实现
    在SysMenuDao接口中定义数据持久化方法:

    int insertObject(SysMenu entity);
    
    

    7.2.3 Mapper映射文件定义

    业务描述与设计实现
    基于SysMenuDao中方法的定义,编写用于实现菜单添加的SQL元素。

    关键代码设计与实现
    在SysMenuMapper.xml中添加insertObject元素,用于写入菜单信息。关键代码如下:

    <insert id="insertObject"
                parameterType="com.cy.pj.sys.entity.SysMenu">
              insert into sys_menus
              (name,url,type,sort,note,parentId,permission,
    createdTime,modifiedTime,createdUser,modifiedUser)
              values
              (#{name},#{url},#{type},#{sort},#{note},#{parentId},
    #{permission},now(),now(),#{createdUser},#{modifiedUser})
     </insert>
    

    7.2.4 Service接口定义及实现

    业务描述与设计实现
    基于控制层请求,调用数据层对象将菜单信息写入到数据库中。

    关键代码设计与实现
    第一步:在SysMenuService接口中,添加用于保存菜单对象的方法。关键代码如下:

    int saveObject(SysMenu entity);
    
    

    第二步:在SysMenuServiceImpl类中,实现菜单保存操作。关键代码如下:

    @Override
    public int saveObject(SysMenu entity) {
    		//1.合法验证
    		if(entity==null)
    		throw new IllegalArgumentException("保存对象不能为空");
    		if(StringUtils.isEmpty(entity.getName()))
    		throw new IllegalArgumentException("菜单名不能为空");
    		//2.保存数据
    		int  rows=sysMenuDao.insertObject(entity);
    		//3.返回数据
    		return rows;
    	}
    
    

    7.2.5 Controller类定义

    业务描述与设计实现
    接收客户端提交的菜单数据,并对其进行封装,然后调用业务层对象进行业务处理,最后将业务层处理结果响应到客户端。

    关键代码设计与实现
    定义Controller方法,借助此方法处理保存菜单数据请求和响应逻辑。关键代码如下:

    @RequestMapping("doSaveObject")
    	public JsonResult doSaveObject(SysMenu entity){
    		sysMenuService.saveObject(entity);
    		return new JsonRehlt("save ok");
    	}
    
    

    7.3 客户端关键业务及代码实现

    7.3.1 页面cancel按钮事件处理

    业务描述与设计实现
    点击页面cancel按钮时,加载菜单那列表页面。

    关键代码设计与实现
    第一步:事件注册(页面加载完成以后)

     $(".box-footer")
    	  .on("click",".btn-cancel",doCancel)
    
    

    第二步:事件处理函数定义

     function doCancel(){
    	var url="menu/menu_list";
    	$("#mainContentId").load(url);  
      }
    
    

    7.3.2 页面Save按钮事件处理

    业务描述与设计实现
    点击页面save按钮时,将页面上输入的菜单信息异步提交到服务端。

    关键代码设计与实现
    第一步:事件注册(页面加载完成以后)。

     $(".box-footer")
    	  .on("click",".btn-save",doSaveOrUpdate)
    
    

    第二步:Save按钮事件处理函数定义。关键代码如下:

    function doSaveOrUpdate(){
    	  //1.获取表单数据
    	  var params=doGetEditFormData();
    	  //2.定义url
    	  var url="menu/doSaveObject";
    	  //3.异步提交数据
    	  $.post(url,params,function(result){
    		  if(result.state==1){
    			  alert(result.message);
    			  doCancel();
    		  }else{
    			  alert(result.message);
    		  }
    	  });
      }
    
    

    第三步:表单数据获取及封装函数定义。关键代码如下:

    function doGetEditFormData(){
    	  var params={
    	      type:$("form input[name='typeId']:checked").val(),
    		name:$("#nameId").val(),
    		url:$("#urlId").val(),
    		sort:$("#sortId").val(),
    		permission:$("#permissionId").val(),
    		parentId:$("#parentId").data("parentId")
    	  }
    	  return params;
      }
    
    

    8.菜单修改页面数据呈现

    8.1 业务时序分析

    当在菜单列表页面中选中某条记录,然后点击修改按钮时,其业务时序分析如图-16所示:

    在这里插入图片描述
    8.2 客户端关键业务及代码实现

    8.2.1 列表页面修改按钮事件处理

    业务描述与设计实现
    点击页面修改按钮时,获取选中菜单记录,并异步加载编辑页面。

    关键代码设计与实现
    第一步:列表页面修改按钮事件注册,关键代码如下:

    $(".input-group-btn")
    .on("click",".btn-update",doLoadEditUI);
    
    

    第二步:修改按钮事件处理函数定义或修改,关键代码如下:

    function doLoadEditUI(){
    	var title;
    	if($(this).hasClass("btn-add")){
    		title="添加菜单"
    	}else if($(this).hasClass("btn-update")){
    		title="修改菜单"
    		//获取选中的记录数据
    		var rowData=doGetCheckedItem();
    		if(!rowData){
    			alert("请选择一个");
    			return;
    		}
    		$("#mainContentId").data("rowData",rowData);
    	}
    	var url="menu/menu_edit";
    	$("#mainContentId").load(url,function(){
    		$(".box-title").html(title);
    	})
    }
    
    

    第三步:获取用户选中记录的函数定义。关键代码如下:

    function doGetCheckedItem(){
    	var tr=$("tbody input[type='radio']:checked")
    	       .parents("tr");
    	return tr.data("rowData");
    }
    
    

    8.2.2 编辑页面菜单数据呈现

    业务描述与设计实现
    页面加载完成,在页面指定位置呈现要修改的数据。

    关键代码设计与实现
    第一步:页面加载完成以后,获取页面div中绑定的数据。关键代码如下:

    $(function(){//假如是修改
    	  var data=$("#mainContentId").data("rowData");
    	  if(data)doInitEditFormData(data);
      });
    
    

    第二步:定义编辑页面数据初始化方法。关键代码如下:

    function doInitEditFormData(data){
    	/*   $("input[type='radio']").each(function(){
    		  if($(this).val()==data.type){
    			  $(this).prop("checked",true);
    		  }
    	  }) */
    	  $(".typeRadio input[value='"+data.type+"']").prop("checked",true);
    	  $("#nameId").val(data.name);
    	  $("#sortId").val(data.sort);
    	  $("#urlId").val(data.url);
    	  $("#permissionId").val(data.permission);
    	  $("#parentId").val(data.parentName);
    	  $("#parentId").data("parentId",data.parentId);
      }
    
    

    9.菜单数据更新实现

    9.1 业务时序分析

    当点击编辑页面更新按钮时,其时序分析如图-17所示:
    在这里插入图片描述
    9.2 服务端关键业务及代码实现

    9.2.1 DAO接口实现

    DAO接口实现

    业务描述与设计实现
    负责将用户编辑页面提交到服务端的菜单数据,更新到数据库进行持久性存储。

    关键代码设计与实现
    在SysMenuDao接口中添加数据更新方法,关键代码如下:

    int updateObject(SysMenu entity);
    
    

    9.2.2 Mapper映射文件定义

    业务描述与设计实现
    基于SysMenuDao中updateObject方法的定义,编写用于实现菜单更新的SQL元素。

    关键代码设计与实现
    在SysMenuMapper.xml中添加updateObject元素,用于更新菜单信息。关键代码如下:

    <update id="updateObject"
                parameterType="com.cy.pj.sys.entity.SysMenu">
             update sys_menus
             set
               name=#{name},
               type=#{type},
               sort=#{sort},
               url=#{url},
               parentId=#{parentId},
               permission=#{permission},
               modifiedUser=#{modifiedUser},
               modifiedTime=now()
            where id=#{id}
        </update>
    
    

    9.2.3 Service接口及实现

    业务描述与设计实现
    基于控制层请求,对数据进行校验并调用数据层对象将菜单信息更新到数据库中。

    关键代码设计与实现
    第一步:在SysMenuService接口中,添加用于更新菜单对象的方法。关键代码如下:

    int updateObject(SysMenu entity);
    
    

    第二步:在SysMenuServiceImpl类中,实现菜单保存操作。关键代码如下:

    @Override
    public int updateObject(SysMenu entity) {
    		//1.合法验证
    		if(entity==null)
    		throw new ServiceException("保存对象不能为空");
    		if(StringUtils.isEmpty(entity.getName()))
    		throw new ServiceException("菜单名不能为空");
    		
    		//2.更新数据
    		int rows=sysMenuDao.updateObject(entity);
    		if(rows==0)
    		throw new ServiceException("记录可能已经不存在");
    		//3.返回数据
    		return rows;
    }
    
    

    9.2.4 Controller类定义

    业务描述与设计实现
    接收客户端提交的菜单数据,并对其进行封装,然后调用业务层对象进行业务处理,最后将业务层处理结果响应到客户端。

    关键代码设计与实现
    定义Controller方法,借助此方法处理保存菜单数据请求和响应逻辑。关键代码如下:

    @RequestMapping("doUpdateObject")
    	public JsonResult doUpdateObject(
    SysMenu entity){
    		sysMenuService.updateObject(entity);
    		return new JsonResult("update ok");
    	}
    
    

    9.3 客户端关键业务及代码实现

    9.3.1 编辑页面更新按钮事件处理

    编辑页面更新按钮事件处理

    业务描述与设计实现
    点击页面save按钮时,将页面上输入的菜单编辑信息提交到服务端。

    关键代码设计与实现
    编辑Save按钮对应的事件处理函数。关键代码如下:

     function doSaveOrUpdate(){
    	  //1.获取表单数据
    	  var params=doGetEditFormData();
    var rowData=$("#mainContentId").data("rowData");
    	  //2.定义url
    	  var insertUrl="menu/doSaveObject";
    	  var updateUrl="menu/doUpdateObject";
    	  var url=rowData?updateUrl:insertUrl;
    	  if(rowData)params.id=rowData.id;
    	  //3.异步提交数据
    	  $.post(url,params,function(result){
    		  if(result.state==1){
    			  alert(result.message);
    			  doCancel();
    		  }else{
    			  alert(result.message);
    		  }
    	  });
      }
    
    

    10.总结

    10.1 重难点分析

    菜单管理在整个系统中的定位(资源管理)。
    菜单数据的自关联查询实现(查询当前菜单以及这个菜单的上级菜单)。
    菜单管理中数据的封装过程(请求数据,响应数据)。
    菜单数据在客户端的呈现。(treeGrid,zTree)

    10.2 FAQ分析

    菜单表是如何设计的,都有哪些字段?
    菜单列表数据在客户端是如何展示的?(TreeGrid)
    菜单删除业务是如何处理的?
    菜单编辑页面中上级菜单数据的呈现方式?(zTree)
    常用表连接方式,如图-18所示:内联接 左外连接 右外连接
    在这里插入图片描述

    展开全文
  • $.get(url,data,function (resp) { // 处理结果 //alert(resp.message) if (resp.success){ $("#userInfo").css("color","green").html(resp.message); }else { $("#userInfo").css("color","red").html(resp....

    黑马旅游网(web阶段综合练习)

    第一天 页面搭建及注册功能

    1 项目搭建

    1.1 项目介绍

    为了巩固web基础知识,提升综合运用能力,故而讲解此案例。要求,每位同学能够独立完成此案例。

    1.2 技术选型

    前台内容使用bootstrap框架结合jq , ajax , json等搭建而成。

    采用了三层架构的设计模式

    1.3 数据库设计

    一个存在八张表,分别代表用户表,地址表,订单表,关系表,商品表,及部分细节表格

    1.4 创建maven项目

    2 用户注册基本功能

    2.1 需求分析

    2.2 代码实现

    页面部分

    Servlet部分

    protected void register(request, response) throws Exception {

    // 1.接收请求参数 map

    request.getParameterMap();

    // 2.封装到User实体

    new User();

    // 3.调用service注册

    userService.register(user);

    // 4.判断

    if (getSuccess()) { // 注册成功

    sendRedirect("/register_ok.jsp");

    } else {// 注册失败

    setAttribute("message", getMessage());

    getRequestDispatcher("register.jsp")

    }

    }

    UserService(接口+实现)

    ResultInfo register(User user);

    UserDao

    /**

    * 根据用户名查询

    */

    User findByUsername(String username);

    /**

    * 根据手机号进行查询

    */

    User findByTelephone(String telephone);

    /**

    * 注册

    */

    void save(User user);

    第二天 注册页文本校验短信验证码等

    校验用户名

    通过ajax技术实现对用户名的校验。通过对此功能的简单修改,实现了输入用户名后,焦点离开文本框后立即给出结果判断此用户名是否已被注册过.

    页面部分

    // 绑定事件

    $("#username").blur(function() {

    // 获取属性值

    let username = this.value;

    // 通过ajax发送请求使用get函数

    let url = '${pageContext.request.contextPath}/UserServlet';

    let data = 'action=ajaxCheackUsername&username='+username;

    $.get(url,data,function (resp) {

    // 处理结果

    //alert(resp.message)

    if (resp.success){

    $("#userInfo").css("color","green").html(resp.message);

    }else {

    $("#userInfo").css("color","red").html(resp.message);

    }

    });

    });

    servlet部分

    protected void ajaxCheackUsername(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 接收请求参数

    String username = request.getParameter("username");

    // 调用service查询

    User user = userService.findByUsername(username);

    // 判断结果

    ResultInfo resultInfo = null;

    // 已存在的情况

    if (user!=null){

    resultInfo = new ResultInfo(false,"用户名已存在");

    }else {

    // 不存在的情况

    resultInfo = new ResultInfo(true,"√");

    }

    // 将结果转为json new ObjectMapper.writeValueAsString

    ObjectMapper objectMapper = new ObjectMapper();

    String s = objectMapper.writeValueAsString(resultInfo);

    // 响应到客户端

    // 声明格式

    response.setContentType("application/json;charset=utf-8");

    response.getWriter().write(s);

    }

    service部分

    @Override

    public User findByUsername(String username) {

    SqlSession sqlSession = MyBatisUtils.openSession();

    UserDao mapper = sqlSession.getMapper(UserDao.class);

    User byUsername = mapper.findByUsername(username);

    MyBatisUtils.release(sqlSession);

    return byUsername;

    }

    阿里云短信服务调用

    1.申请账号,选择服务

    申请签名是最难的一部分,如果申请不到请耐心反复申请,请坚持下来,一定不要放弃希望,相信奇迹一定会出现的.

    2.引入sdk

    导入默认的工具文件

    填入自己申请到的accessKey,就可以调用了

    验证码是自己生成的随机数

    手机号前台获取

    验证码保存到session

    // 签名

    String signName = "MRYHL技术栈";

    // 模版

    String templateCode = "##########";

    // json模板参数

    String param = "{\"code\":\"" + code + "\"}";

    try {

    SendSmsResponse sendSmsResponse = SmsUtils.sendSms(telephone, signName, templateCode, param);

    if (sendSmsResponse.getCode().equalsIgnoreCase("ok")){

    System.out.println(code);

    return new ResultInfo(true,"发送成功");

    }

    } catch (ClientException e) {

    e.printStackTrace();

    }

    return new ResultInfo(false,"服务器忙...");

    第三天 登陆与地址管理

    1.登陆模块

    1.1 账号密码登陆

    1.2 验证码登陆

    2.个人信息修改

    3.地址管理

    4.完成了修改 删除 设置默认的功能

    第四天 导航分类查询

    1.导航表

    需求分析

    绑定页面加载事件

    向服务器发请求ajax ajaxFIndAll

    将返回结果遍历,展示分析

    页面导航栏内容存在mysql的数据库中,第一次访问的时候先从数据库中获取到内容,保存在缓存数据库redis中.之后的每一次调用都是在redis中查询.

    2.Redis介绍与使用

    简介

    Redis(Remote Dictionary Server)是用C语言开发的一个开源的高性能键值对数据库。它的所有数据都是保存在内存中的,这也就决定了其读写速度之快,是其它硬盘保存数据的系统所无法匹敌的。

    官方曾经给出过一组测试数据,50个并发执行100000个请求: 读的速度是110000次/s,写的速度是81000次/s

    数据结构

    Redis采用的是键值对存储,键的类型只能为字符串,值支持五种数据类型:

    字符串:String

    哈希:HashMap

    双向链表:LinkedList

    无序集合:HashSet

    有序集合:LinkedHashSet

    2.1 导航栏内容redis加载

    缓存一致性问题

    解决方案,我们对缓存相关进行增删改时,清空缓存,不要直接去修改数据库,通过java语言来实现功能(修改完毕后,可以通过jedis删除缓存数据),解决了缓存一致性的问题

    第五天 列表分页显示,查询功能,购物车功能是实现.

    列表分页需求分析

    前台页面

    servlet

    service

    Dao + xml

    查询需求分析

    详情展示

    多表查询

    关联查询(inner join),底层会产生笛卡尔积,三张表以上不建议使用

    嵌套查询,底层不会产生笛卡尔积,但是操作步骤繁琐,开发使用不多【本次使用】

    单表查询,java组合,互联网项目使用最多方案

    servlet

    service

    routedao

    前台页面

    购物车功能,添加购物车,查看购物车,删除购物项

    购物车相关内容

    实体类

    工具类

    servlet

    service

    生成订单

    创建OrderServlet

    在session获取user对象

    查询收货地址

    从redis中获取购物车对象

    将数据写入到request

    转发到视图

    protected void orderInfo(HttpServletRequest requsest, HttpServletResponse response) throws ServletException, IOException {

    // 获取session中的用户对象

    User currentUser = (User) requsest.getSession().getAttribute("currentUser");

    // 查询收货地址

    List

    addressList = addressService.findByUid(String.valueOf(currentUser.getUid()));

    // 从redis中获取购物车

    Cart cart = CartUtils.redis2Cart(currentUser.getUid());

    // 将数据写入到request中

    requsest.setAttribute("addressList",addressList);

    requsest.setAttribute("cart",cart);

    // 转发视图

    requsest.getRequestDispatcher("/order_info.jsp").forward(requsest,response);

    }

    设置过滤器

    @WebFilter(urlPatterns = {"/home_index.jsp","/home_left.jsp","/AddressServlet","/CartServlet","/OrderServlet"})

    修改前台显示order_info.jsp

    • ${entry.value.route.rname}
      7天无理由退货
    • ¥${entry.value.route.price}
    • X${entry.value.num}
    • 有货
    展开全文
  • 01travel_注册功能

    2020-06-03 16:05:22
    MavenProject:点击+,然后找到项目所在的位置,选择travel项目的pom.xml文件,点击ok,完成项目导入。需要等待一小会,项目初始化完成。 3、启动项目 方式一:MavenProject -->Plugins -->tomcat7 -->...

    旅游网综合案例

    1、前言

    为了巩固web基础知识,提升综合运用能力,故而讲解此案例。要求,每位同学能够独立完成此案例。

    2、项目导入

    MavenProject:点击+,然后找到项目所在的位置,选择travel项目的pom.xml文件,点击ok,完成项目导入。需要等待一小会,项目初始化完成。

    3、启动项目

    1. 方式一:MavenProject -->Plugins -->tomcat7 -->tomcat7:run
    2. 方式二:配置maven快捷启动
      1.
      2.在这里插入图片描述

    4、技术选型

    1. web层
      • Servlet:前端控制器
      • HTML:视图,效果展示
      • Filter:过滤器:web组件之一
      • BeanUtils:封装JavaBean
      • Jackson:json序列化工具
    2. Service层
      • Javamail:Java发送邮件的工具
      • Redis:nosql内存数据库
      • Jedis:java的redis客户端
    3. Dao层
      • mysql:数据库 -->后期可以用Mybatis代替
      • Druid:数据库连接池
      • JDBCTemplate:jdbc的工具

    5、创建数据库

    5.1、创建数据库

    create database travel;
    

    5.2、创建表

    6、注册功能实现

    register.html

    1. 使用js完成表单校验
    2. 使用ajax完成表单提交
    3. 注册成功,跳转到成功页面

    RegisterUserServlet

    1. 设置编码(filter)
    2. 封装User对象
    3. 调用service完成注册
    4. 根据service的返回,提示信息
      1. 将提示的信息转为json,并返回客户端
      2. 设置响应的头信息 contentType

    UserService

    registerUser(User user):service中,注册方法

     		1. 调用dao,根据用户名查询用户
          * 用户存在,直接返回false,不让注册
          * 不存在,调用dao保存用户信息,完成注册
    

    UserDao

    findUserByUsername(String username):根据用户名,查询用户信息,判断用户是否已经被注册过

    registerUser(User user):UserDao中的注册方法,上面的方法查询不到时再注册。

    7、前台代码

    7.1、表单校验

    表单校验:在组件失去焦点时或者submit时校验,如果校验通过的话就可以提交,反之不能提交

    1. 用户名:数字和单词组成,5–20位

      var reg_username = /^\w{5,20}$/;
      
    2. 密码:数字和单词组成,8–20位

      var reg_password = /^\w{8,20}$/;
      
    3. 邮箱:正常的邮箱格式

      var reg_email = /^\w+@\w+\.\w+$/;
      
    4. 姓名:不能为空

    5. 手机号码:不能为空

    6. 出生日期:不能为空

    7. 验证码:不能为空

      校验正则表达式

      //校验用户名:单词字符,5--20位
      function checkUsername() {
          //1获取用户名的值
          var username = $("#username").val();
          //2定义正则
          var reg_username = /^\w{5,20}$/;
          //3判断,给出提示信息
          var flag = reg_username.test(username);
          if (flag) {
              //用户名合法
              $("#username").css("border", "")
          } else {
              //用户名非法:加一个红色的边框
              $("#username").css("border", "1px solid red")
              alert("用户名不合法,应为5-20位的字符,请重新输入!!");
          }
          return flag;
      }
      

      检验不能为空

      //姓名校验:不能为空
      function checkName() {
          //1获取姓名的值
          var name = $("#name").val();
          var flag;
          if (name != "") {
              //姓名合法
              $("#name").css("border", "")
              flag = true;
          } else {
              //姓名不合法
              $("#name").css("border", "1px solid red")
              flag = false;
          }
          return flag;
      }
      

      如果校验不通过,就不提交表单

      $(function () {
          //当提交表单时,调用所有的校验方法
          //如果这个方法没有返回值,或者返回值为true,则表单提交
          $("#registerForm").submit(function () {
               return (checkUsername() && checkPassword() && checkEmail() && checkName() &&
                  checkTelephone() && checkCheckcode()         
          });
      
          //当某一个组件失去焦点时,调用对应的校验方法
          $("#username").blur(checkUsername);
          $("#password").blur(checkPassword);
          $("#email").blur(checkEmail);
          $("#name").blur(checkName);
          $("#telephone").blur(checkTelephone);
          $("#check").blur(checkCheckcode);
      });
      

    7.2、 异步ajax提交表单

    我们使用异步表单提交是为了能够获取服务器响应的数据,因为我们前台使用的是html页面作为视图层,不能够直接从servlet相关的域对象中获取值,只能通过ajax获取响应数据。

    $("#registerForm").submit(function () {
        if (checkUsername() && checkPassword() && checkEmail() && checkName() &&
            checkTelephone() && checkCheckcode()) {
            //1如果各种校验都通过,则发送ajax请求,提交表单数据
            $.post("registerUserServlet", $(this).serialize(), function (data) {
                //处理服务器响应回来的数据信息
    
            });
        }
    
        //不让页面跳转
        return false;
    });
    

    8、后台代码

    8.1Servlet

    RegisterUserServlet

    1. 校验验证码

      if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)) {
          //验证码错误
          ResultInfo info = new ResultInfo();
          info.setFlag(false);
          info.setErrorMsg("验证码输入错误!!!");
      
          //将info对象序列化为json
          ObjectMapper mapper = new ObjectMapper();
          String json = mapper.writeValueAsString(info);
      
          //将json数据写会客户端,并且设置content-type
          response.setContentType("application/json;charset=utf-8");
          response.getWriter().write(json);
      
          return ;
      }
      
    2. 获取数据

      //获取数据
      Map<String, String[]> map = request.getParameterMap();
      
    3. 封装对象

    //封装对象
    User user = new User();
    try {
        //将map集合中的数据,封装到对应的user对象中的属性中去
        BeanUtils.populate(user, map);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    
    1. 调用service完成注册
    //3调用service完成注册
    UserService service = new UserServiceImpl();
    boolean register = service.registerUser(user);
    
    1. 响应结果
    //4响应结果
    if (register) {
        //如果注册成功
        info.setFlag(true);
        info.setErrorMsg("注册成功");
    }else {
        info.setFlag(false);
        info.setErrorMsg("用户名重复,注册失败!!!");
    }
    
    //5将info对象序列化为json
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(info);
    
    //将json数据写会客户端,并且设置content-type
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write(json);
    

    8.2、Service

    UserService和UserServiceImpl

    public boolean registerUser(User user) {
        //1根据用户名查询用户对象,判断该用户是否被注册
        User u = dao.findUserByUsername(user.getUsername());
        if(u != null) {
            //用户名存在,注册失败
            return false;
        }
        //2保存注册的用户信息
        dao.registerUser(user);
    
        return true;
    }
    

    8.3、Dao

    UserDao和UserDaoImpl

    public User findUserByUsername(String username)
    public void registerUser(User user)
    

    8、邮件激活

    为什么要进行邮件激活?为了保证用户填写的邮箱是正确的,将来可以推广一些宣传信息到用户邮箱中

    8.1、发送邮件

    这里:发现一个问题, 不知道是不是我的网速不好还是哪里配置没有写对,发邮件的话会导致注册成功特别慢,因为发邮件,网络会有延迟,注册信息通过json返回到客户端特别慢 , 我开了多线程,但是还是有问题,这里就不做了,如果有大神知道的话,帮助一下我!!!

    展开全文
  • 一起Travel吧!(日志模块) 1.日志管理设计说明 1.1 业务设计说明 本模块主要是实现对用户行为日志(例如谁在什么时间点执行了什么操作,访问了哪些方法,传递的什么参数,执行时长等)进行记录、查询、删除等操作。其表...

    一起Travel吧! (日志模块)

    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.3 API设计说明

    日志业务后台API分层架构及调用关系如图-2所示:
    在这里插入图片描述
    说明:分层目的主要将复杂问题简单化,实现各司其职,各尽所能。

    2.日志管理列表页面呈现

    2.1 业务时序分析

    当点击首页左侧的"日志管理"菜单时,其总体时序分析如图-3所示:
    在这里插入图片描述

    2.2 服务端实现

    2.2.1 Controller实现

    业务描述与设计实现:
    基于日志管理的请求业务,在PageController中添加doLogUI方法,doPageUI方法分别用于返回日志列表页面,日志分页页面。

    关键代码设计与实现:
    第一步:在PageController中定义返回日志列表的方法。代码如下:

    @RequestMapping("log/log_list")
    public String doLogUI() {
    	return "sys/log_list";
    }
    
    

    第二步:在PageController中定义用于返回分页页面的方法。代码如下:

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

    2.3 客户端实现

    2.3.1 日志菜单事件处理

    业务描述与设计:
    首先准备日志列表页面(/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异步请求函数。

    2.3.2 日志列表页面事件处理

    业务描述与设计实现
    当日志列表页面加载完成以后异步加载分页页面(page.html)。

    关键代码设计与实现:
    在log_list.html页面中异步加载page页面,这样可以实现分页页面重用,哪里需要分页页面,哪里就进行页面加载即可。关键代码如下:

    $(function(){
    	$("#pageId").load("doPageUI");
    });
    
    

    说明:数据加载通常是一个相对比较耗时操作,为了改善用户体验,可以先为用户呈现一个页面,数据加载时,显示数据正在加载中,数据加载完成以后再呈现数据。这样也可满足现阶段不同类型客户端需求(例如手机端,电脑端,电视端,手表端。)

    3.日志管理列表数据呈现

    日志查询服务端数据基本架构,如图-4所示。
    在这里插入图片描述

    3.2 服务端API架构及业务时序图分析

    服务端日志分页查询代码基本架构,如图-5所示:
    在这里插入图片描述

    服务端日志列表数据查询时序图,如图-6所示:
    在这里插入图片描述

    3.3 服务端关键业务及代码实现

    3.3.1 Entity类实现

    业务描述及设计实现
    构建实体对象(POJO)封装从数据库查询到的记录,一行记录映射为内存中一个的这样的对象。对象属性定义时尽量与表中字段有一定的映射关系,并添加对应的set/get/toString等方法,便于对数据进行更好的操作。

    关键代码分析及实现:

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

    说明:通过此对象除了可以封装从数据库查询的数据,还可以封装客户端请求数据,实现层与层之间数据的传递。
    思考:这个对象的set方法,get方法可能会在什么场景用到?

    3.3.2 Dao接口实现

    业务描述及设计实现
    通过数据层对象,基于业务层参数数据查询日志记录总数以及当前页要呈现的用户行为日志信息。

    关键代码分析及实现:
    第一步:定义数据层接口对象,通过将此对象保证给业务层以提供日志数据访问。代码如下:

    @Mapper
    public 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"/>
     </select>
    
    

    第四步:在映射文件中添加id为findPageObjects元素,实现分页查询。代码如下:

    <select id="findPageObjects"
                resultType="com.cy.pj.sys.entity.SysLog">
             select *
             <include refid="queryWhereId"/>
    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 java.util.List;
     
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
     
    import com.cy.pj.sys.entity.SysLog;
     
    @SpringBootTest
    public class SysLogDaoTests {
     
    	   @Autowired
    	   private SysLogDao sysLogDao;
    	   
    	   @Test
    	   public void testGetRowCount() {
    		   int rows=sysLogDao.getRowCount("admin");
    		   System.out.println("rows="+rows);
    	   }
    	   @Test
    	   public void testFindPageObjects() {
    		   List<SysLog> list=
    		   sysLogDao.findPageObjects("admin", 0, 3);
    		   for(SysLog log:list) {
    			   System.out.println(log);
     			}
    	   }
    }
    

    3.3.4 Service接口及实现类

    业务描述与设计实现:
    业务层主要是实现模块中业务逻辑的处理。在日志分页查询中,业务层对象首先要通过业务方法中的参数接收控制层数据(例如username,pageCurrent)并校验。然后基于用户名进行总记录数的查询并校验,再基于起始位置及页面大小进行当前页记录的查询,最后对查询结果进行封装并返回。

    关键代码设计及实现:
    业务值对象定义,基于此对象封装数据层返回的数据以及计算的分页信息,具体代码参考如下:

    package com.cy.pj.common.bo;
    public class PageObject<T> 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<T> records;
    public PageObject(){}
    	public PageObject(Integer pageCurrent, Integer pageSize, Integer rowCount, List<T> records) {
    		super();
    		this.pageCurrent = pageCurrent;
    		this.pageSize = pageSize;
    		this.rowCount = rowCount;
    		this.records = records;
    //		this.pageCount=rowCount/pageSize;
    //		if(rowCount%pageSize!=0) {
    //			pageCount++;
    //		}
    		this.pageCount=(rowCount-1)/pageSize+1;
    	}
    	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<T> getRecords() {
    		return records;
    	}
    	public void setRecords(List<T> records) {
    		this.records = records;
    	} 
    }
    
    

    定义日志业务接口及方法,暴露外界对日志业务数据的访问,其代码参考如下:

    package com.cy.pj.sys.service;
    public interface SysLogService {
    	     /**
          * @param name 基于条件查询时的参数名
          * @param pageCurrent 当前的页码值
          * @return 当前页记录+分页信息
          */
    	 PageObject<SysLog> findPageObjects(
    			 String username,
    			 Integer pageCurrent);
    }
    
    

    日志业务接口及实现类,用于具体执行日志业务数据的分页查询操作,其代码如下:

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

    说明:几乎在所有的框架中都提供了自定义异常,例如MyBatis中的BindingException等。

    定义Service对象的单元测试类,代码如下:

    package com.cy.pj.sys.service;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
     
    import com.cy.pj.common.vo.PageObject;
    import com.cy.pj.sys.entity.SysLog;
     
    @SpringBootTest
    public class SysLogServiceTests {
     
    	@Autowired
    	private SysLogService sysLogService;
    	@Test
    	public void testFindPageObjects() {
    	   PageObject<SysLog> pageObject=
    	   sysLogService.findPageObjects("admin", 1);
    	   System.out.println(pageObject);
    	   
    	}
    }
    
    

    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所示:在这里插入图片描述

    3.4 客户端关键业务及代码实现

    3.4.1 客户端页面事件分析

    当用户点击首页日志管理时,其页面流转分析如图-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所示:
    在这里插入图片描述

    result 结果对象分析,如图-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+")");
    
    

    第二步:分页页面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所示。
    在这里插入图片描述

    4.2 删除业务时序分析

    客户端提交删除请求,服务端对象的工作时序分析,如图-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>
    
    

    思考:如上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/doDeleteObjects?ids=1,2,3
    
    

    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 服务端关键业务及代码实现

    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所示:
    在这里插入图片描述

    6.总结

    重难点分析
    日志管理整体业务分析与实现。
    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.异常处理规则:能处理则处理,不能处理则抛出。

    思考
    用户行为日志是如何实现分页查询的?(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 中的异步操作如何实现?
    项目中的BUG分析及解决套路?(排除法,打桩,断点)

    常见BUG
    (略)

    展开全文
  • 29 alert("删除成功!!"); 30 } 31 32 } 注意的是:success:function(data){}中的data是后台返回的数据如果是render返回的页面 那data就是文件的字符串页面如果是HttpResponse,那data就是接收的字符串。 后端接收...
  • 但是那样处理不太好,因为可能A页面中还有很多其他的公共方法,比如美化过的Alert等,所以如果能做一个公共的方法来接收C页面传过来的值会更加方便之后的框架调整,而且也不建议总是去修改页面C。 ...
  • 下载 笔记版/无笔记版 pdf资料: GitHub - zhbink/LiuLiYueDu: 流利阅读pdf汇总 本文内容全部来源于流利阅读。...The Google and Apple app that helps Saudi men limit female relatives’ travel 谷歌...
  • 黑马旅游网笔记

    2020-10-20 21:20:18
    },//响应成功后的回调函数 error:function () { alert("出错啦...") },//表示如果请求响应出现错误,会执行的回调函数 dataType:"text"//设置接受到的响应数据的格式 }); 2. $.get():发送get请求 * 语法:$.get...
  • 一起来看: 下面附上BaseServlet的代码,看一眼就可以懂 package cn.itcast.travel.web.servlet; import com.fasterxml.jackson.core.JsonProcessingException; import ...
  • 'vuex:travel-to-state' , function (targetState) { store.replaceState(targetState); }); store.subscribe( function (mutation, state) { devtoolHook.emit( 'vuex:mutation' , mutation, state);...
  • material-design-icons

    千次阅读 2018-08-16 15:46:54
    card_travel e8f8   casino eb40   cast e307   cast_connected e308   center_focus_strong e3b4   center_focus_weak e3b5   change_history e86b ...
  • Android 开源项目及库汇总

    千次阅读 2018-11-26 21:44:55
     – sweet-alert-dialog是一款清新文艺的 Android 弹窗, 灵感来自于 JS 版的 SweetAlert。 进度条 easyloadingbtn  – 模仿了一个Dribbble上的Material Design效果,环形loading, 进度条、进度圈。 ...
  • Android中的设计模式

    2017-04-28 23:16:40
    _dialog_alert) .setView (R .layout .myview ) .setPositiveButton (R .string .positive , new DialogInterface .OnClickListener () { @Override public void onClick(DialogInterface dialog, int which) ...
  • openerp - asterisk connector(转载)

    千次阅读 2015-07-17 23:56:45
    openerp - asterisk connector(转载) 原文:http://www.akretion.com/open-source-contributions/openerp-asterisk-voip-connector OpenERP - Asterisk connector ... an OpenS
  • F8App-ReactNative项目源码分析4-js篇

    万次阅读 2016-06-04 21:34:32
    本文开始分析f8app核心js部分的源码,这篇文章将非常难理解,原因了Redux框架引入了很多新概念,使用了大量函数式编程思想,建议先把后面的参考文章仔细过一遍,确保理解后再看本文。React Native的理念是Learn once...
  • 数据库、表和前端的资料都再这里 ...提取码:n32q CategoryDaoImpl .java package cn.itcast.travel.dao.impl; import cn.itcast.travel.dao.CategoryDao;...import cn.itcast.travel.util.JDBCUtils
  • Application Fundamentals——应用程序基础知识Key classes——关键类ActivityServiceBroadcastReceiverContentProviderIntentIn this document——在这篇文章中Application Components——应用程序组件Activating ...
  • And so, among the pleasures of learning, we should include travel, travel with an open mind, an alert eye and a wish to understand other peoples, other places, rather than looking in them for a ...
  • //alert(search);//?id=5 // 切割字符串,拿到第二个值 var cid = search.split("=")[1];*/ //获取cid的参数值 var cid = getParameter("cid"); //获取rname的参数值 var rname = getParameter("rname"); //判断...
  • url:'travel.yes/garden/request', success:function(data) { alert(data); } }); }) }; <p>And this is my Controller public function request() { $qty = $this->input->post('qty'); ...
  • alert 日志记录了数据库的很多重要信息,要养成时常检查alert日志的习惯,但如果日志很大vi打开翻来覆去找着麻烦,怎么做的可以查错呢? 看我的测试 [oracle@ahjcyl-db bdump]$ tail -n 1000 alert_ahjcyl.log |...
  • List of Game enging form wiki

    千次阅读 2013-01-17 13:46:20
    This week we are launching Wikivoyage. ...Join us in creating a free travel guide that anyone can edit. List of game engines From Wikipedia, the free encyclopedia Many tools
  • iOS 各版本中的新特性(What's New in iOS)- 目录翻译完成
  • 《黑马旅游网》综合案例 前言 为了巩固web基础知识,提升综合运用能力,故而...选择travel项目的pom.xml文件,点击ok,完成项目导入。需要等待一小会,项目初始化完???? 转存失败重新上传取消 启动项目 ...
  • Well, the index could be used, for example, to find out which communities to warn if a flood alert was issued for a particular river. Perhaps it could be used, in a drier region, to predict where ...
  • 黑马旅游网_后端总结

    万次阅读 2019-05-25 11:48:01
    <path>/travel 为了不每次启动项目都要去找这个命令,我们可以配置一下。后面我们启动直接点击启动的那个按钮即可  各个表的解释 tab_user  用户表,存储的和用户相关的信息 /** *...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,253
精华内容 501
关键字:

alerttravel