我使用的是springboot框架,写系统日志之前需要先引入pom依赖:
org.springframework.boot spring-boot-starter-aop
1.日志表 (我使用的时postgre,其他数据库需要修改脚本)
-- public.sys_log definition -- Drop table -- DROP TABLE sys_log; CREATE TABLE sys_log ( id varchar(38) NOT NULL, username varchar(30) NOT NULL, "method" varchar(255) NULL, -- 方法名 params varchar(1000) NULL, -- 参数 ip varchar(20) NULL, create_date timestamp(0) NULL, "type" varchar(20) NULL, -- 类型 : 查询、新增等等 model varchar(50) NULL, -- 模块 "result" varchar(50) NULL, -- 操作结果 description varchar(255) NULL, url varchar(400) NULL, -- 请求url ConSTRAINT log_pk PRIMARY KEY (id) ); -- Column comments COMMENT ON COLUMN public.sys_log."method" IS '方法名'; COMMENT ON COLUMN public.sys_log.params IS '参数'; COMMENT ON COLUMN public.sys_log."type" IS '类型 : 查询、新增等等'; COMMENT ON COLUMN public.sys_log.model IS '模块'; COMMENT ON COLUMN public.sys_log."result" IS '操作结果'; COMMENT ON COLUMN public.sys_log.url IS '请求url';
2. 日志实体
import java.util.Date;
import javax.persistence.*;
import lombok.Data;
@Data
@Table(name = "sys_log")
public class SysLog {
@Id
private String id;
@Column(name = "username")
private String username; //用户名
@Column(name = "method")
private String method; //方法名
@Column(name = "params")
private String params; //参数
@Column(name = "ip")
private String ip; //ip地址
@Column(name = "url")
private String url; //请求url
@Column(name = "type")
private String type; //操作类型 :新增、删除等等
@Column(name = "model")
private String model; //模块
@Column(name = "create_date")
private Date createDate; //操作时间
@Column(name = "result")
private String result; //操作结果
@Column(name = "description")
private String description;//描述
}
3. dao (继承了tk.mapper中的Mymapper,所以没有写插入日志方法),因没有业务相关代码,所以没有写service以及的impl
import org.springframework.stereotype.Repository; import com.album.manager.pojo.SysLog; import tk.mapper.MyMapper; @Repository public interface LogMapper extends MyMapper{ }
4. 自定义注解类(用于拦截操作方法并插入日志)
import java.lang.annotation.documented;
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)//注解在哪个阶段执行
@documented
public @interface SysLogAnnotation {
String operModul() default ""; // 操作模块
String operType() default ""; // 操作类型
String operDesc() default ""; // 操作说明
}
5. AOP拦截插入日志类:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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 org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.album.manager.common.utils.IPUtils;
import com.album.manager.common.utils.SysLogAnnotation;
import com.album.manager.dao.LogMapper;
import com.album.manager.pojo.SysLog;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import cn.hutool.core.lang.id.NanoId;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class SysLogAspect {
@Autowired
LogMapper logDao;
@Pointcut("@annotation(com.album.manager.common.utils.SysLogAnnotation)")
public void operLogPoinCut() {
}
@AfterReturning(returning = "result", value = "operLogPoinCut()")
public void saveOperLog(JoinPoint joinPoint, Object result) throws Throwable {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
SysLog sysLog = new SysLog();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
SysLogAnnotation annotation = method.getAnnotation(SysLogAnnotation.class);
if (annotation != null) {
sysLog.setModel(annotation.operModul());
sysLog.setType(annotation.operType());
sysLog.setDescription(annotation.operDesc());
}
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
sysLog.setMethod(methodName); // 类名.请求方法
sysLog.setCreateDate(new Date()); //操作时间
//操作用户 --登录时有把用户的信息保存在session中,可以直接取出
String user = (String)request.getSession().getAttribute("user");
sysLog.setUsername(user);
sysLog.setIp(IPUtils.getIpAddr(request)); //操作IP IPUtils工具类网上大把的,比如工具类集锦的hutool.jar
sysLog.setUrl(request.getRequestURI()); // 请求URI
// 方法请求的参数
Map rtnMap = converMap(request.getParameterMap());
// 将参数所在的数组转换成json
String params = JSON.toJSonString(rtnMap);
//获取json的请求参数
if (rtnMap == null || rtnMap.size() == 0) {
params = getJsonStrByRequest(request);
}
sysLog.setParams(params); // 请求参数
Map dataResult = (Map )result; //返回值信息
//需要先判断返回值是不是Map ,如果不是會拋異常,需要控制层的接口返回数据格式统一
//如果嫌返回格式统一太麻烦建议日志保存时去掉操作结果
sysLog.setResult(dataResult.get("msg").toString()); //獲取方法返回值中的msg,如果上面的類型錯誤就拿不到msg就會拋異常
//保存日志
sysLog.setId(NanoId.randomNanoId());
logDao.insert(sysLog);
} catch (Exception e) {
e.printStackTrace();
log.error("日誌記錄異常,請檢查返回值是否是Map 類型");
}
}
public Map converMap(Map paramMap) {
Map rtnMap = new HashMap();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
public String getJsonStrByRequest(HttpServletRequest request) {
String param = null;
try {
BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
JSonObject jsonObject = JSONObject.parseObject(responseStrBuilder.toString());
param = jsonObject.toJSonString();
System.out.println(param);
} catch (Exception e) {
e.printStackTrace();
}
return param;
}
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "n");
}
String message = exceptionName + ":" + exceptionMessage + "nt" + strbuff.toString();
return message;
}
}
6. Controller层接口(也是操作被拦截的地方):
@RequestMapping(value = "/setUser", method = RequestMethod.POST)
@ResponseBody
@SysLogAnnotation(operModul = "系统管理>>用户管理", operType = "新增", operDesc = "新增用户")
public Map setUser(baseAdminUser user) {
Map data = new HashMap();
if(user.getId().isBlank()){
data = adminUserService.addUser(user);
}
return data;
}
7. 特别特别特别需要注意,如果系统日志中需要插入操作结果,那么就一定需要统一返回格式,比如上面代码中返回结果 Map@Transactional(rollbackFor = Exception.class)
public Map addUser(baseAdminUser user) {
Map data = new HashMap();
try {
baseAdminUser old = baseAdminUserMapper.getUserByUserName(user.getSysUserName(),null);
if(old != null){
data.put("code",0);
data.put("msg","用户名已存在!");
log.error("用户[新增],结果=用户名已存在!");
return data;
}
String phone = user.getUserPhone();
if(phone.length() != 11){
data.put("code",0);
data.put("msg","手机号位数不对!");
log.error("设置用户[新增或更新],结果=手机号位数不对!");
return data;
}
String username = user.getSysUserName();
if(user.getSysUserPwd() == null){
String password = DigestUtils.Md5(username,"123456");
user.setSysUserPwd(password);
}else{
String password = DigestUtils.Md5(username,user.getSysUserPwd());
user.setSysUserPwd(password);
}
user.setRegTime(DateUtils.getCurrentDate());
user.setUserStatus(1);
user.setId(UUID.randomUUID().toString());
int result = baseAdminUserMapper.insert(user);
if(result == 0){
data.put("code",0);
data.put("msg","新增失败!");
log.error("用户[新增],结果=新增失败!");
return data;
}
data.put("code",1);
data.put("msg","新增成功!");
log.info("用户[新增],结果=新增成功!");
} catch (Exception e) {
e.printStackTrace();
data.put("code",0);
data.put("msg","新增异常失败!");
log.error("用户[新增]异常!", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackonly();//捕捉的异常需要手动回滚
}
return data;
}
上面service代码中返回的map格式定义有没有code都可以,视页面处理而定,如果系统操作日志需要保存结果,那么这个msg就必须要有,步骤5中的 AOP拦截插入日志类中获取操作结果时获取的就是map中的msg
分享个工具类jar 的依赖(官网:Hutool参考文档 , API文档:hutool-码云(gitee.com)):
cn.hutool hutool-all5.7.19



