三、日志管理(2)--删除、添加数据操作实现


一、日志管理删除操作实现


1.1 数据架构分析

当用户执行日志删除操作时,客户端与服务端交互时的基本数据架构,如图所示:
在这里插入图片描述

1.2 删除业务时序分析

客户端提交删除请求,服务端对象的工作时序分析,如图所示:
在这里插入图片描述

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

1.3.1 Dao接口实现
  • 业务描述及设计实现

数据层基于业务层提交的日志记录id,进行日志删除操作。

  • 关键代码设计及实现:

在SysLogDao中添加基于id执行日志删除的方法。代码参考如下:

int deleteObjects(@Param("ids")Integer… ids);
1.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="," ="id">
               #{id} 
        </foreach>
    </delete>

FAQ分析:如上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的语法。

1.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;
}
1.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

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

1.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();
   }

说明:最后将如上方法添加在删除操作成功以后的代码块中。

二、日志管理数据添加实现

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

2.1.1 Dao接口实现
  • 业务描述与设计实现

数据层基于业务层的持久化请求,将业务层提交的用户行为日志信息写入到数据库。

  • 关键代码设计与实现

在SysLogDao接口中添加用于实现日志信息持久化的方法。关键代码如下:

int insertObject(SysLog entity);
2.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>
2.1.3 Service接口及实现类
  • 业务描述与设计实现

将日志切面中抓取到的用户行为日志信息,通过业务层对象方法持久化到数据库。

  • 关键代码实现

第一步:在SysLogService接口中,添加保存日志信息的方法。关键代码如下:

void saveObject(SysLog entity) 

第二步:在SysLogServiceImpl类中添加,保存日志的方法实现。关键代码如下:

	@Override
	public void saveObject(SysLog entity) {
	  sysLogDao.insertObject(entity);
}
2.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;
	}
}

原理分析,如图所示:
在这里插入图片描述

三、总结

3.1 重难点分析

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

3.2 FAQ分析

  • 用户行为日志表中都有哪些字段?(面试时有时会问)
  • 用户行为日志是如何实现分页查询的?---- 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 中的异步操作如何实现?
  • Spring 中的@Async如何应用?
  • 项目中的BUG分析及解决套路?(排除法,打桩,断点)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

经理,天台风好大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值