动吧旅游日志管理设计说明(二)

日志管理删除操作实现

1.1数据架构分析

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

1.2删除业务时序分析

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

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

1.3.1Dao接口实现

业务描述及设计实现

持久层基于业务层提交的日志记录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相当于elese的语法。

1.3. 3 Service接口及实现类

业务描述与设计实现

在日志业务层定义用于执行删除业务的方法,首先通过方法参数接受控制层传递的多个记录的id,并对参数id进行校验,然后基于日志记录id执行删除业务实现。最后返回业务执行结果

关键代码实现

第一步:在SysLogService接口中,添加基于多个id进行日志删除的方法。关键代码如下:

int deleteObjects(Integer… ids) {}

第二步:在SysLogServiceImpl接口中,添加基于多个id进行日志删除的方法。关键代码如下:

@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.4Controller类实现

业务描述与设计实现

第一步:在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

二、日志管理添加实现

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

2.1.1 Dao接口实现

业务描述设计实现

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

关键代码实现

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

int insertObject(SysLog entity);

2.1.2Mapper映射文件

业务描述与设计实现

基于SysLogDao中方法的定义,编写用于数据持久化的sql元素。

关键代码设计与实现

int insertObject(SysLog entity);

2.2.1 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;
	}
}

原理分析,如图所示:

在这里插入图片描述

三、总结

日志管理整体业务分析与实现。
(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
发出的红包

打赏作者

ZangChen.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值