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 页面设计
1.3 分页API设计
1.4 分页业务时序分析
2 日志管理列表页面呈现
2.1 PageController的实现
基于日志管理的请求业务,在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.2 客户端实现
2.2.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.2.2 日志页面分页异步加载
$(function(){
$("#pageId").load("doPageUI");
});
3 业务实现
3.1 日志管理实现
查询:
数据架构分析
日志分页架构分析
时序图分析:
删除:
日志删除架构分析:
日志删除时序图分析:
第一步:创建SysLog实体类
package com.cy.pj.sys.pojo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
//实现此接口的对象可以进行序列化和反序列化
//1)序列化:将对象转化为字节的过程,转换以后便于通过网络进行传输或存储到相关介质中
//2)反序列化:将字节转换为对象的过程
//建议:在java中所有用于存储数据的对象都实现Serializable接口
@Data
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;
}
第二步:创建SysLogDao层
package com.cy.pj.sys.dao;
import com.cy.pj.sys.pojo.SysLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysLogDao {
int insertObject(SysLog entity);
int deleteObjects(@Param("ids") Integer... ids);
/**
* @param username 查询条件(例如查询哪个用户的日志信息)
* @return 总记录数(基于这个结果可以计算总页数)
*/ //int getRowCount(@Param("username") String username);
/**
* @param username 查询条件(例如查询哪个用户的日志信息)
* @param startIndex 当前页的起始位置
* @param pageSize 当前页的页面大小
* @return 当前页的日志记录信息
* 数据库中每条日志信息封装到一个SysLog对象中
*/
List<SysLog> findPageObjects(String username);
}
//// @Param("startIndex")Integer startIndex,
//// @Param("pageSize")Integer pageSize);
第三步:创建mapper文件映射
<?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">
<insert id="insertObject">
insert into sys_logs
(username,operation,method,params,time,ip,createdTime)
values
(#{username},#{operation},#{method},#{params},#{time},#{ip},#{createdTime})
</insert>
<delete id="deleteObjects">
delete from sys_Logs
<where>
<if test="ids!=null and ids.length>0">
id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</if>
or 1=2
</where>
</delete>
<sql id="queryWhereId">
from sys_Logs
<where>
<if test="username!=null and username!=''">
username like concat("%",#{username},"%")
</if>
</where>
</sql>
<select id="getRowCount" resultType="int">
select count(*)
<include refid="queryWhereId"/>
</select>
<!-- <select id="findPageObjects"-->
<!-- resultType="com.cy.pj.sys.pojo.SysLog">-->
<!-- select *-->
<!-- <include refid="queryWhereId"/>-->
<!-- order by createdTime desc-->
<!-- limit #{startIndex},#{pageSize}-->
<!-- </select>-->
<select id="findPageObjects"
resultType="com.cy.pj.sys.pojo.SysLog">
select *
<include refid="queryWhereId"/>
order by createdTime desc
</select>
</mapper>
第四步:创建service接口及实现类
创建pojo类PageOject类:
package com.cy.pj.common.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
//借助此对象封装业务逻辑结果
@Data
@NoArgsConstructor
public class PageObject<T> implements Serializable {//类泛型:类名<泛型> 约束类中属性,方法参数以及返回值类型
private static final long serialVersionUID = -3130527491950235344L;
/**当前页的页码值*/
private Integer pageCurrent=1;
/**页面大小*/
private Integer pageSize=3;
/**总行数(通过查询获得)*/
private Integer rowCount=0;
/**总页数(通过计算获得)*/
private Integer pageCount=0;
/**当前页记录*/
private List<T> records;
public PageObject(Integer pageCurrent, Integer pageSize, Integer rowCount, List<T> records) {
this.pageCurrent = pageCurrent;
this.pageSize = pageSize;
this.rowCount = rowCount;
this.pageCount=rowCount/pageSize;
this.records = records;
if(this.rowCount%this.pageSize!=0)this.pageCount++;
}
}
创建SysLogService接口:
package com.cy.pj.sys.servive;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.pojo.SysLog;
public interface SysLogService {
void saveObject(SysLog entity);
int deleteObjects(Integer... Ids);
/**
* @param username 基于条件查询时的参数名
* @param pageCurrent 当前的页码值
* @return 当前页记录+分页信息
*/
PageObject<SysLog> findPageObjects(
String username,
Integer pageCurrent);
}
创建SysLogServiceImpl实现类:
package com.cy.pj.sys.servive.impl;
import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.exception.ServiceException;
import com.cy.pj.sys.dao.SysLogDao;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.servive.SysLogService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SysLogServiceImpl implements SysLogService {
@Autowired
private SysLogDao sysLogDao;
@Override
public void saveObject(SysLog entity) {
sysLogDao.insertObject(entity);
}
//@requirespermission注解描述的方法为一个权限切入点方法,当登录用户访问此方法时需要授权。
//那如何检测用户是否有访问此方法的权限?
//第一:获取访问此方法时需要的权限标识:"sys:log:delete"
//第二:获取登陆用户拥有的菜单访问的权限标识
//第三:判定用户拥有的权限标识中是否包含访问时需要的授权标识,假如包含则授权访问。
@RequiresPermissions("sys:log:delete")
@RequiredLog("日志删除")
@Override
public int deleteObjects(Integer... ids) {
//1,参数校验
if(ids==null||ids.length==0)
throw new IllegalArgumentException("必须提供正确的id值");
//2,基于id删除日志
int rows=sysLogDao.deleteObjects(ids);
//3,检验结果并返回
if(rows==0)
throw new ServiceException("记录可能已经不存在");
return rows;
}
@RequiredLog("日志查询")
@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=5;
Page<SysLog> page=PageHelper.startPage(pageCurrent, pageSize);
//3.2)计算startIndex
// int startIndex=(pageCurrent-1)*pageSize;
//3.3)执行当前数据的查询操作
// List<SysLog> records=
// sysLogDao.findPageObjects(name, startIndex, pageSize);
List<SysLog> records=
sysLogDao.findPageObjects(name);
//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 new PageObject<>(pageCurrent,pageSize,(int)page.getTotal(),records);
}
}
设置自定义异常处理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
}
}
第五步:创建Controller类
创建pojo返回值对象JsonResult:
package com.cy.pj.common.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
//封装服务端响应到客户端的数据
@Data
@NoArgsConstructor
public class JsonResult implements Serializable {
private static final long serialVersionUID = 8518978402852081033L;
//SysResult/Result/R
/**状态码*/
private Integer state=1;//1表示SUCCESS,0表示ERROR
/**状态信息*/
private String message="ok";
/**正确数据*/
private Object data;
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();
}
}
创建SysLogController:
package com.cy.pj.sys.controller;
import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.pojo.JsonResult;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.servive.SysLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/log/")
public class SysLogController {
@Autowired
private SysLogService sysLogService;
@RequestMapping("doDeleteObjects")
public JsonResult doDeleteObjects(Integer... ids){
sysLogService.deleteObjects(ids);
return new JsonResult("Delete ok");
}
//在Controller类中添加分页请求处理方法,代码参考如下:
@RequestMapping("doFindPageObjects")
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 com.cy.pj.common.pojo.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//JDK中的自带的日志API
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JsonResult doHandleRuntimeException(
RuntimeException e){
e.printStackTrace();//也可以写日志
log.error("exception msg {}"+e.getMessage());
//异常信息
return new JsonResult(e);//封装
}
@ExceptionHandler(ShiroException.class)
@ResponseBody
public JsonResult doHandleShiroException(
ShiroException e) {
JsonResult r=new JsonResult();
r.setState(0);
if(e instanceof UnknownAccountException) {
r.setMessage("账户不存在");
}else if(e instanceof LockedAccountException) {
r.setMessage("账户已被禁用");
}else if(e instanceof IncorrectCredentialsException) {
r.setMessage("密码不正确");
}else if(e instanceof AuthorizationException) {
r.setMessage("没有此操作权限");
}else {
r.setMessage("系统维护中");
}
e.printStackTrace();
return r;
}
}
日志管理AOP实现
第一步:自定义注解RequiredLog
package com.cy.pj.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//定义我们的注解可以修饰谁
@Retention(RetentionPolicy.RUNTIME)//定义我们的注解何时有效
public @interface RequiredLog {
String value() default "";
}
第二步:切面类SysLogAspect实现
package com.cy.pj.common.aspect;
import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.util.IPUtils;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.servive.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
/**
* @Aspect 注解描述的对象为一个切面对象,在切面对象中定义
* 1)切入点(Pointcut):织入扩展功能的一些连接点的集合
* 2)通知方法(Advice):封装了扩展逻辑的方法
*/
@Slf4j
@Aspect
@Component
public class SysLogAspect {
//通过Pointcut定义一个切入点,@annotation方式为定义切入点的一种方式,
//在这里表示业务对象中由com.cy.pj.common.annotation.RequiredLog注解描述的方法为一些切入点方法
@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
public void doLog(){}//doLog方法仅仅是@Pointcut注解的一个载体,方法体内不需要写任何内容
/**
* @Around 注解描述的方法可以在目标方法执行之前和之后做功能扩展
* @param joinPoint 封装了目标方法信息的一个对象(连接点对象)
* @return 目标方法的执行结果
* @throws Throwable
*/ @Around("doLog()")
public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
try {
long t1 = System.currentTimeMillis();
Object result = joinPoint.proceed();//去调用目标方法,其返回值为目标方法返回值
long t2 = System.currentTimeMillis();
System.out.println("time:" + (t2 - t1));
//将正常的用户行为日志写入到数据库
saveSysLog(joinPoint, (t2 - t1));
return result;
}catch(Throwable e){
//saveErrorLog(...);也可以将错误日志写入到数据库
logError(joinPoint,e.getMessage());
throw e;
}
}
//将错误日志进行输出并记录
private void logError(ProceedingJoinPoint joinPoint,String exceptionMsg) throws JsonProcessingException {
String targetClassName=joinPoint.getTarget().getClass().getName();
String methodName=joinPoint.getSignature().getName();
String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
log.error("error.msg->{}->{}->{}",targetClassName+"."+methodName,params,exceptionMsg);
}
@Autowired
private SysLogService sysLogService;
private void saveSysLog(ProceedingJoinPoint joinPoint,long time) throws NoSuchMethodException, JsonProcessingException {
//1.获取用户行为日志
//获取目标对象类型
Class<?> targetClass=joinPoint.getTarget().getClass();
//获取目标方法的签名信息
MethodSignature ms=(MethodSignature) joinPoint.getSignature();
//获取目标方法?(类中方法的唯一标识是什么:方法名+参数列表)
Method targetMethod=targetClass.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
//获得RequiredLog
RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
//获取操作名(RequiredLog中operation的值)
String operation= requiredLog.value();
//2.封装日志信息
SysLog entity=new SysLog();
entity.setUsername("cgb");//将来这个位置为登录用户名
entity.setIp(IPUtils.getIpAddr());
entity.setOperation(operation);//为目标方法指定的一个名字
entity.setMethod(targetClass.getName()+"."+targetMethod.getName());//类全名+方法名
//entity.setParams(Arrays.toString(joinPoint.getArgs()));//调用方法时传递实际参数
entity.setParams(new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
entity.setTime(time);
entity.setCreatedTime(new Date());
//3.保存用户行为值
//sysLogService.saveObject(entity);
//异步写日志(自己new thread,借助池中线程,但非tomcat线程池中线程)
new Thread(){//1M
@Override
public void run() {
sysLogService.saveObject(entity);
}
}.start();
}
}
原理分析:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。