又又又又来了!这次学习日志的自定义注解,在项目中,需要对一系列操作进行记录,因此一个日志注解还是有必要的。
1、引入依赖
以下依赖并不全是用于自定义注解的,就不删了。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!--devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--生产配置元数据-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<!--aspect-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!--lang-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.7</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!--swagger-ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2、日志注解类
import com.example.demo.aspect.LogEnum;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoLog {
//日志内容
String value() default "";
/**
* 日志类型
* @return 0:操作日志;1:登录日志;2:定时任务;
*/
int logType() default LogEnum.LOG_TYPE_2;
/**
* 操作日志类型
* @return 1:查询;2:添加;3:修改;4:删除;
*/
int operateType() default 0;
}
3、日志及操作类型
public interface LogEnum {
/**
* 系统操作日志:登录
*/
public static final int LOG_TYPE_1 = 1;
/**
* 系统操作日志:操作
*/
public static final int LOG_TYPE_2 = 2;
/**
* 操作日志类型:查询
*/
public static final int OPERATE_TYPE_1 = 1;
/**
* 操作日志类型:添加
*/
public static final int OPERATE_TYPE_2 = 2;
/**
* 操作日志类型:更新
*/
public static final int OPERATE_TYPE_3 = 3;
/**
* 操作日志类型:删除
*/
public static final int OPERATE_TYPE_4 = 4;
/**
* 操作日志类型:导入
*/
public static final int OPERATE_TYPE_5 = 5;
/**
* 操作日志类型:导出
*/
public static final int OPERATE_TYPE_6 = 6;
}
4、日志切面类
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.example.demo.aspect.annotation.AutoLog;
import com.example.demo.domain.SysUser;
import com.example.demo.domain.SysUserOld;
import com.example.demo.domain.dto.LogDTO;
import com.example.demo.service.BaseCommonService;
import com.example.demo.utils.IPUtils;
import com.example.demo.utils.SpringContextUtils;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
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.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Date;
/**
* 系统日志,切面处理类
*/
@Aspect
@Component
public class AutoLogAspect {
@Resource
private BaseCommonService baseCommonService;
//定义的注解类位置
@Pointcut("@annotation(com.example.demo.aspect.annotation.AutoLog)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSysLog(point, time, result);
return result;
}
//保存日志的内容
private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogDTO dto = new LogDTO();
AutoLog syslog = method.getAnnotation(AutoLog.class);
if(syslog != null){
//update-begin-author:taoyan date:
String content = syslog.value();
/*if(syslog.module()== ModuleType.ONLINE){
content = getOnlineLogContent(obj, content);
}*/
//注解上的描述,操作日志内容
dto.setLogType(syslog.logType());
dto.setLogContent(content);
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
dto.setMethod(className + "." + methodName + "()");
//设置操作类型
if (dto.getLogType() == LogEnum.LOG_TYPE_2) {
dto.setOperateType(getOperateType(methodName, syslog.operateType()));
}
//获取request
//通过 @RequestParam(name = "x")String x 形式获取
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//请求的参数
dto.setRequestParam(getReqestParams(request,joinPoint));
//设置IP地址
dto.setIp(IPUtils.getIpAddr(request));
//获取requestUrl
dto.setRequestUrl(request.getRequestURI());
//获取requestType
dto.setRequestType(request.getMethod());
//获取登录用户信息
SysUserOld sysUser = (SysUserOld) SecurityUtils.getSubject().getPrincipal();
if(sysUser!=null){
dto.setUserid(sysUser.getUsername());
//dto.setUsername(sysUser.getRealname());
dto.setUsername(sysUser.getUsername());
}
//耗时
dto.setCostTime(time);
dto.setCreateTime(new Date());
//保存系统日志
baseCommonService.addLog(dto);
}
/**
* 获取操作类型
*/
private int getOperateType(String methodName,int operateType) {
if (operateType > 0) {
return operateType;
}
if (methodName.startsWith("list")) {
return LogEnum.OPERATE_TYPE_1;
}
if (methodName.startsWith("add")) {
return LogEnum.OPERATE_TYPE_2;
}
if (methodName.startsWith("edit")) {
return LogEnum.OPERATE_TYPE_3;
}
if (methodName.startsWith("delete")) {
return LogEnum.OPERATE_TYPE_4;
}
if (methodName.startsWith("import")) {
return LogEnum.OPERATE_TYPE_5;
}
if (methodName.startsWith("export")) {
return LogEnum.OPERATE_TYPE_6;
}
return LogEnum.OPERATE_TYPE_1;
}
/**
* @Description: 获取请求参数
* @author: scott
* @date: 2020/4/16 0:10
* @param request: request
* @param joinPoint: joinPoint
* @Return: java.lang.String
*/
private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
String httpMethod = request.getMethod();
String params = "";
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
Object[] paramsArray = joinPoint.getArgs();
// java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
// https://my.oschina.net/mengzhang6/blog/2395893
Object[] arguments = new Object[paramsArray.length];
for (int i = 0; i < paramsArray.length; i++) {
if (paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
//ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
//ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
continue;
}
arguments[i] = paramsArray[i];
}
//update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
PropertyFilter profilter = new PropertyFilter() {
@Override
public boolean apply(Object o, String name, Object value) {
if(value!=null && value.toString().length()>500){
return false;
}
return true;
}
};
params = JSONObject.toJSONString(arguments, profilter);
//update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
} else {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
for (int i = 0; i < args.length; i++) {
params += " " + paramNames[i] + ": " + args[i];
}
}
}
return params;
}
/**
* online日志内容拼接
* @param obj
* @param content
* @return
*/
/*private String getOnlineLogContent(Object obj, String content){
if (Result.class.isInstance(obj)){
Result res = (Result)obj;
String msg = res.getMessage();
String tableName = res.getOnlTable();
if(oConvertUtils.isNotEmpty(tableName)){
content+=",表名:"+tableName;
}
if(res.isSuccess()){
content+= ","+(oConvertUtils.isEmpty(msg)?"操作成功":msg);
}else{
content+= ","+(oConvertUtils.isEmpty(msg)?"操作失败":msg);
}
}
return content;
}*/
/* private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
AutoLog syslog = method.getAnnotation(AutoLog.class);
if(syslog != null){
//update-begin-author:taoyan date:
String content = syslog.value();
if(syslog.module()== ModuleType.ONLINE){
content = getOnlineLogContent(obj, content);
}
//注解上的描述,操作日志内容
sysLog.setLogContent(content);
sysLog.setLogType(syslog.logType());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//设置操作类型
if (sysLog.getLogType() == CommonConstant.LOG_TYPE_2) {
sysLog.setOperateType(getOperateType(methodName, syslog.operateType()));
}
//获取request
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//请求的参数
sysLog.setRequestParam(getReqestParams(request,joinPoint));
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
//获取登录用户信息
LoginUser sysUser = (LoginUser)SecurityUtils.getSubject().getPrincipal();
if(sysUser!=null){
sysLog.setUserid(sysUser.getUsername());
sysLog.setUsername(sysUser.getRealname());
}
//耗时
sysLog.setCostTime(time);
sysLog.setCreateTime(new Date());
//保存系统日志
sysLogService.save(sysLog);
}*/
}
5、配置类-接口返回数据格式
import lombok.Data;
import java.io.Serializable;
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 1l;
//成功标志
private Boolean success;
//返回代码
private Integer code;
//返回处理消息
private String message;
//返回数据对象 data
private T data;
//时间戳
private long timestamp = System.currentTimeMillis();
public R(){}
public static R<Object> ok(){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
return r;
}
public static R<Object> ok(String msg){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(msg);
return r;
}
public static R<Object> ok(Object data){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
r.setData(data);
return r;
}
public static<T> R<T> OK(){
R<T> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
return r;
}
public static<T> R<T> OK(T data){
R<T> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
r.setData(data);
return r;
}
public static<T> R<T> OK(String msg,T data){
R<T> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(msg);
r.setData(data);
return r;
}
/**
* 请求失败
*/
public static R<Object> error(){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.FAILED.getSuccess());
r.setCode(ResultEnum.FAILED.getCode());
r.setMessage(ResultEnum.FAILED.getMessage());
return r;
}
public static R<Object> error(String msg){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.FAILED.getSuccess());
r.setCode(ResultEnum.FAILED.getCode());
r.setMessage(msg);
return r;
}
/**
* 请求无权限
*/
public static R<Object> unanthorized(){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.UNAUTHORIZED.getSuccess());
r.setCode(ResultEnum.UNAUTHORIZED.getCode());
r.setMessage(ResultEnum.UNAUTHORIZED.getMessage());
return r;
}
public R<T> setResult(ResultEnum resultEnum){
R<T> r = new R<>();
r.setSuccess(resultEnum.getSuccess());
r.setCode(resultEnum.getCode());
r.setMessage(resultEnum.getMessage());
return r;
}
public R<T> success(Boolean status){
this.setSuccess(status);
return this;
}
public R<T> code(Integer code){
this.setCode(code);
return this;
}
public R<T> message(String message){
this.setMessage(message);
return this;
}
}
6、枚举类-接口返回数据情况枚举
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public enum ResultEnum {
SUCCESS(true,200,"操作成功"),
FAILED(false,300,"操作失败"),
NO_OPERATOR_AUTH(false,301,"无操作权限"),
DATA_ERROR(false,302,"数据错误"),
DATA_NO_EXIST(false,303,"数据不存在"),
UNAUTHORIZED(false,401,"用户认证失败");
private final Boolean success;
private final Integer code;
private final String message;
ResultEnum(Boolean success,Integer code,String message){
this.success=success;
this.code=code;
this.message=message;
}
}
7、实体类
import com.example.demo.domain.User;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 日志对象
* cloud api 用到的接口传输对象
*/
@Data
public class LogDTO implements Serializable {
private static final long serialVersionUID = 8482720462943906924L;
/**内容*/
private String logContent;
/**日志类型(0:操作日志;1:登录日志;2:定时任务) */
private Integer logType;
/**操作类型(1:添加;2:修改;3:删除;) */
private Integer operateType;
/**登录用户 */
private User sysUser;
private Long id;
private String createBy;
private Date createTime;
private Long costTime;
private String ip;
/**请求参数 */
private String requestParam;
/**请求类型*/
private String requestType;
/**请求路径*/
private String requestUrl;
/**请求方法 */
private String method;
/**操作人用户名称*/
private String username;
/**操作人用户账户*/
private String userid;
public LogDTO(){
}
public LogDTO(String logContent, Integer logType, Integer operatetype){
this.logContent = logContent;
this.logType = logType;
this.operateType = operatetype;
}
public LogDTO(String logContent, Integer logType, Integer operatetype, User sysUser){
this.logContent = logContent;
this.logType = logType;
this.operateType = operatetype;
this.sysUser = sysUser;
}
}
8、mapper层
import com.example.demo.domain.dto.LogDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface BaseCommonMapper {
@Select("insert into sys_log (id, log_type, log_content, operate_type, userid, username, ip, method, request_url, request_param, request_type, cost_time, create_time)\n" +
" values(\n" +
" #{dto.id,jdbcType=VARCHAR},\n" +
" #{dto.logType,jdbcType=INTEGER},\n" +
" #{dto.logContent,jdbcType=VARCHAR},\n" +
" #{dto.operateType,jdbcType=INTEGER},\n" +
" #{dto.userid,jdbcType=VARCHAR},\n" +
" #{dto.username,jdbcType=VARCHAR},\n" +
" #{dto.ip,jdbcType=VARCHAR},\n" +
" #{dto.method,jdbcType=VARCHAR},\n" +
" #{dto.requestUrl,jdbcType=VARCHAR},\n" +
" #{dto.requestParam,jdbcType=VARCHAR},\n" +
" #{dto.requestType,jdbcType=VARCHAR},\n" +
" #{dto.costTime,jdbcType=BIGINT},\n" +
" #{dto.createTime,jdbcType=TIMESTAMP}\n" +
" )")
void saveLog(@Param("dto") LogDTO dto);
}
9、service层、serviceImpl层
import com.example.demo.domain.SysUser;
import com.example.demo.domain.dto.LogDTO;
import org.springframework.stereotype.Service;
@Service
public interface BaseCommonService {
/**
* 保存日志
* @param logDTO
*/
void addLog(LogDTO logDTO);
/**
* 保存日志
* @param logContent
* @param logType
* @param operateType
* @param user
*/
void addLog(String logContent, Integer logType, Integer operateType, SysUser user);
/**
* 保存日志
* @param logContent
* @param logType
* @param operateType
*/
void addLog(String logContent, Integer logType, Integer operateType);
}
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.example.demo.domain.SysUser;
import com.example.demo.domain.dto.LogDTO;
import com.example.demo.mapper.BaseCommonMapper;
import com.example.demo.service.BaseCommonService;
import com.example.demo.utils.IPUtils;
import com.example.demo.utils.SpringContextUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Service
public class BaseCommonServiceImpl implements BaseCommonService {
@Autowired
BaseCommonMapper baseCommonMapper;
@Override
public void addLog(LogDTO logDTO) {
if((logDTO.getId())==null){
logDTO.setId(IdWorker.getId());
//logDTO.setId(String.valueOf(IdWorker.getId()));
}
baseCommonMapper.saveLog(logDTO);
}
@Override
public void addLog(String logContent, Integer logType, Integer operateType, SysUser user) {
LogDTO sysLog = new LogDTO();
sysLog.setId(IdWorker.getId());
//注解上的描述,操作日志内容
sysLog.setLogContent(logContent);
sysLog.setLogType(logType);
sysLog.setOperateType(operateType);
try {
//获取request
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
} catch (Exception e) {
sysLog.setIp("127.0.0.1");
}
//获取登录用户信息
if(user==null){
try {
user = (SysUser) SecurityUtils.getSubject().getPrincipal();
} catch (Exception e) {
//e.printStackTrace();
}
}
if(user!=null){
sysLog.setUserid(user.getUsername());
sysLog.setUsername(user.getRealname());
}
sysLog.setCreateTime(new Date());
//保存系统日志
baseCommonMapper.saveLog(sysLog);
}
@Override
public void addLog(String logContent, Integer logType, Integer operateType) {
addLog(logContent, logType, operateType, null);
}
}
10、使用注解
在controller层的接口方法处引用注解@AutoLog。
public static Log logger=LogFactory.getLog(LoginController.class);
@AutoLog("用户登录")
@PostMapping(value = "/login")
public String login(@RequestParam("phonenum") String phoneNum,
@RequestParam("passwd") String passWd){
for(User user:userList){
if(user.getPassWd().equals(passWd)&&user.getPhoneNum().equals(phoneNum)){
logger.info("loginpost 方法访问");
return user.getUserName()+"欢迎登陆";
}
}
return "登录失败";
}
【补充】以上内容没有过多描述,后续再完善,根据jeecgboot框架学习。