五、Spring Boot之旅

引入需要的依赖

之前都是用MySQL数据库,有朋友私信我说能不能用Oracle,这次我就听取这位朋友的意见加入Oracle数据库来进行讲解,使用AOP通过配合自定义注解来实现操作的监控。首先就是搭建一个基本的Spring Boot Web环境开启Spring Boot,然后引入必要依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
		
<!-- JdbcTemplate -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- aop依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- oracle驱动 -->
<dependency>
	<groupId>com.oracle</groupId>
	<artifactId>ojdbc6</artifactId>
	<version>11.2.0.1.0</version>
</dependency>

<!-- druid数据源驱动 -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.1.21</version>
</dependency>

自定义日志注解

先定义一个@Log注解,用它于标注我们需要监控的方法:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

创建库表和实体

CREATE TABLE "DGAUDIT"."SYS_LOG" (
   "ID" NUMBER(20) NOT NULL ,
   "USERNAME" VARCHAR2(50 BYTE) NULL ,
   "OPERATION" VARCHAR2(50 BYTE) NULL ,
   "TIME" NUMBER(11) NULL ,
   "METHOD" VARCHAR2(200 BYTE) NULL ,
   "PARAMS" VARCHAR2(500 BYTE) NULL ,
   "IP" VARCHAR2(64 BYTE) NULL ,
   "CREATE_TIME" DATE NULL 
);

COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."USERNAME" IS '用户名';
COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."OPERATION" IS '用户操作';
COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."TIME" IS '响应时间';
COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."METHOD" IS '请求方法';
COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."PARAMS" IS '请求参数';
COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."IP" IS 'IP地址';
COMMENT ON COLUMN "DGAUDIT"."SYS_LOG"."CREATE_TIME" IS '创建时间';

--添加自增序列字段,从1开始每次增量为1
CREATE SEQUENCE seq_sys_log START WITH 1 INCREMENT BY 1;

创建库表对应的实体类:

public class SysLog implements Serializable{
	
	/**
	 * @Fields serialVersionUID :TODO
	 */
	private static final long serialVersionUID = 1L;
	
	private Integer id;
    private String username;
    private String operation;
    private Integer time;
    private String method;
    private String params;
    private String ip;
    private Date createTime;
    
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getOperation() {
		return operation;
	}
	public void setOperation(String operation) {
		this.operation = operation;
	}
	public Integer getTime() {
		return time;
	}
	public void setTime(Integer time) {
		this.time = time;
	}
	public String getMethod() {
		return method;
	}
	public void setMethod(String method) {
		this.method = method;
	}
	public String getParams() {
		return params;
	}
	public void setParams(String params) {
		this.params = params;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	public Date getCreateTime() {
		return createTime;
	}
	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}
   
}

配置application.properties

# 指定数据源的类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
spring.datasource.username=dgaudit
spring.datasource.password=dgaudit
 
#datasource druid pool
spring.datasource.druid.filters= stat
spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=8

#druid监控配置
# WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
#是否启用StatFilter默认值true
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#session统计功能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
#最大session数
#spring.datasource.druid.web-stat-filter.session-stat-max-count=100000
#你可以配置principalSessionName,使得druid能够知道当前的session的用户是谁
spring.datasource.druid.web-stat-filter.principal-session-name=admin
#你可以配置principalSessionName,使得druid能够知道当前的cookie的用户是谁
spring.datasource.druid.web-stat-filter.principal-cookie-name=admin
#置profileEnable能够监控单个url调用的sql列表。
spring.datasource.druid.web-stat-filter.profile-enable=true
# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
spring.datasource.druid.stat-view-servlet.enabled= true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
#需要账号密码才能访问控制台
spring.datasource.druid.stat-view-servlet.login-username=user
spring.datasource.druid.stat-view-servlet.login-password=password
#IP白名单
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#IP黑名单(共同存在时,deny优先于allow)
#spring.datasource.druid.stat-view-servlet.deny=192.168.10.1
# Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置
# Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔
spring.datasource.druid.aop-patterns= org.lsh.dubhe.service.*
#配置wall filter
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.filter.wall.db-type=mysql
spring.datasource.druid.filter.wall.config.alter-table-allow=false
spring.datasource.druid.filter.wall.config.truncate-allow=false
spring.datasource.druid.filter.wall.config.drop-table-allow=false
#是否允许非以上基本语句的其他语句,缺省关闭,通过这个选项就能够屏蔽DDL。
spring.datasource.druid.filter.wall.config.none-base-statement-allow=false
#检查UPDATE语句是否无where条件,这是有风险的,但不是SQL注入类型的风险
spring.datasource.druid.filter.wall.config.update-where-none-check=true
#SELECT ... INTO OUTFILE 是否允许,这个是mysql注入攻击的常见手段,缺省是禁止的
spring.datasource.druid.filter.wall.config.select-into-outfile-allow=false
#是否允许调用Connection.getMetadata方法,这个方法调用会暴露数据库的表信息
spring.datasource.druid.filter.wall.config.metadata-allow=true
#对被认为是攻击的SQL进行LOG.error输出
spring.datasource.druid.filter.wall.log-violation=true
#对被认为是攻击的SQL抛出SQLExcepton
#spring.datasource.druid.filter.wall.throw-exception=true

保存日志DAO及实现类

这里为了方便,就直接使用Spring JdbcTemplate来操作数据库了。定义一个SysLogDao接口,包含一个保存操作日志的抽象方法:

public interface SysLogDao {
	public void saveSysLog(SysLog sysLog);
}

实现类代码如下:

@Repository
public class SysLogDaoImpl implements SysLogDao {
	
	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public void saveSysLog(SysLog sysLog) {
		
		StringBuffer sql = new StringBuffer("insert into sys_log ");
		sql.append("(id,username,operation,time,method,params,ip,create_time) ");
		sql.append("values(seq_sys_log.nextval,:username,:operation,:time,:method,");
		sql.append(":params,:ip,:createTime)");

		NamedParameterJdbcTemplate np = new NamedParameterJdbcTemplate(this.jdbcTemplate.getDataSource());
		np.update(sql.toString(), new BeanPropertySqlParameterSource(sysLog));

	}

}

切面与切点

定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知:

@Aspect
@Component
public class LogAspect {
	
	@Autowired
	private SysLogDao sysLogDao;
	
	@Pointcut("@annotation(com.xuyangnian.annotations.Log)")
	public void pointcut() {}
	
	@Around("pointcut()")
	public Object around(ProceedingJoinPoint point) {
		Object result = null;
		long beginTime = System.currentTimeMillis();
		try {
			//执行方法
			result = point.proceed();
		}catch(Throwable e) {
			e.printStackTrace();
		}
		//执行时长(毫秒)
		long time = System.currentTimeMillis()- beginTime;
		//保存日志
		saveLog(point,time);
		return result;
	}
	
	private void saveLog(ProceedingJoinPoint joinPoint,long time) {
		 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
	        Method method = signature.getMethod();
	        SysLog sysLog = new SysLog();
	        Log logAnnotation = method.getAnnotation(Log.class);
	        if (logAnnotation != null) {
	            // 注解上的描述
	            sysLog.setOperation(logAnnotation.value());
	        }
	        // 请求的方法名
	        String className = joinPoint.getTarget().getClass().getName();
	        String methodName = signature.getName();
	        //保存方法名
	        sysLog.setMethod(className + "." + methodName + "()");
	        // 请求的方法参数值
	        Object[] args = joinPoint.getArgs();
	        // 请求的方法参数名称
	        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
	        String[] paramNames = u.getParameterNames(method);
	        if (args != null && paramNames != null) {
	            String params = "";
	            for (int i = 0; i < args.length; i++) {
	                params += "  " + paramNames[i] + ": " + args[i];
	            }
	            //保存参数
	            sysLog.setParams(params);
	        }
	        // 获取request
	        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
	        // 设置IP地址
	        sysLog.setIp(IPUtils.getIpAddr(request));
	        // 模拟一个用户名
	        sysLog.setUsername("净晨");
	        //响应时间
	        sysLog.setTime((int) time);
	        sysLog.setCreateTime(new Date());
	        // 保存系统日志
	        sysLogDao.saveSysLog(sysLog);
	}

}

在定义的LogAspect类中,我封装了一个获取HTTP类和一个获取真实IP类,模块代码如下:

/**
 * 
 * @ClassName::HttpContextUtils
 * @Description: 获取HTTP请求
 * @author :许扬念 
 * @date :2020年4月15日 下午3:27:13
 *
 */
public class HttpContextUtils {
	public static HttpServletRequest getHttpServletRequest() {
		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	}
}
/**
 * 
 * @ClassName::IPUtils
 * @Description: 获取真实IP地址类
 * @author :许扬念 
 * @date :2020年4月15日 下午3:20:58
 *
 */
public class IPUtils {

	public static String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
	}

}

测试

创建测试controller:

@RestController
public class TestLogController {

	@Log("执行方法一")
	@GetMapping("/one")
	public void methodOne(String name) {
	}

	@Log("执行方法二")
	@GetMapping("/two")
	public void methodTwo() throws InterruptedException {
		Thread.sleep(2000);
	}

	@Log("执行方法三")
	@GetMapping("/three")
	public void methodThree(String name, String age) {
	}

}

启动项目,访问
http://localhost:8080/one?name=美美
http://localhost:8080/two
http://localhost:8080/three?name=阿兰&age=18
在这里插入图片描述
好了本期的Spring Boot讲解就到这里了,欢迎大家的阅读,我是许扬念,我们下期再见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值