主要实现是通过ApiLog记录接口请求的所有日志,然后将日志对象先记录到redis的list中,然后通过动态定时器定时将redis中的日志对象写入到数据库中。
一、自定义注解
自定义注解ApiLog,主要包含三个参数:
第一个参数为接口操作类型,在我的项目中分为两种,一种是登录认证类,一种是接口请求类
第二个参数为操作描述,可以在使用自定义注解时手动输入
第三个参数为模块名称
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {
/**
public final static int AUTH_LOGIN = 0;
public final static int APPLY_RES = 1; **/
int type();
String actionDescrible();
String module();
}
二、注解逻辑类
逻辑主要分为三块:
第一块:beforeLog,主要记录接口请求的一些基本参数,如接口地址,请求方式,请求参数等等
第二块:AfterReturning,主要记录接口正常返回的日志
第三块:AfterThrowing,主要记录接口异常时返回的日志报错信息
@Component
@Aspect
@Order(1)
@Slf4j
public class ApiLogAspect {
private static final ThreadLocal<BasicLogEntity> threadLocalBasicLog = new ThreadLocal();
private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
long startTime;
long endTime;
@Autowired
UserMapper userMapper;
@Autowired
RedisClient redis;
public ApiLogAspect() {
}
@Before("@annotation(apiLog)")
public void beforeLog(JoinPoint jp,ApiLog apiLog){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
MethodSignature sign = (MethodSignature) jp.getSignature();
Method method = sign.getMethod();
BasicLogEntity basicLogEntity = new BasicLogEntity();
this.fillBasicLog(basicLogEntity,jp,request,apiLog,method);
threadLocalBasicLog.set(basicLogEntity);
startTime = System.currentTimeMillis();
}
@AfterThrowing(
value = "@annotation(api)",
throwing = "ex"
)
public void exceptionLog(ApiLog api, Exception ex) {
BasicLogEntity basicLog = (BasicLogEntity) threadLocalBasicLog.get();
UserEntity userEntity=new UserEntity();
if(StringUtils.isBlank(basicLog.idCard)){
if(StpUtil.isLogin()){
String idcard= StpUtil.getLoginIdAsString();
if(StringUtils.isBlank(idcard)){
userEntity.setUsername("未登录");
userEntity.setIdCard("未登录");
}else{
userEntity.setIdCard(idcard);
userEntity=userMapper.selectOne(userEntity);
if(userEntity==null){
userEntity.setUsername("未登录");
userEntity.setIdCard("未登录");
}
}
}else {
userEntity.setUsername("未登录");
userEntity.setIdCard("未登录");
}
basicLog.username=userEntity.getUsername();
basicLog.idCard=userEntity.getIdCard();
}
basicLog.success = 0;
String msg = ex.toString();
basicLog.exceptionMsg = msg.length() <= 128 ? msg : msg.substring(0, 128);
this.redis.listPush("savelog", basicLog);
}
@AfterReturning(
value = "@annotation(api)",
returning = "res"
)
public void returnLog(ApiLog api, Result res) {
BasicLogEntity basicLog = (BasicLogEntity) threadLocalBasicLog.get();
UserEntity userEntity=new UserEntity();
if(StringUtils.isBlank(basicLog.idCard)){
if(StpUtil.isLogin()){
String idcard= StpUtil.getLoginIdAsString();
if(StringUtils.isBlank(idcard)){
userEntity.setUsername("未登录");
userEntity.setIdCard("未登录");
}else{
userEntity.setIdCard(idcard);
userEntity=userMapper.selectOne(userEntity);
if(userEntity==null){
userEntity.setUsername("未登录");
userEntity.setIdCard("未登录");
}
}
}else {
userEntity.setUsername("未登录");
userEntity.setIdCard("未登录");
}
basicLog.username=userEntity.getUsername();
basicLog.idCard=userEntity.getIdCard();
}
basicLog.notice = res.getMsg();
basicLog.success = 1;
this.redis.listPush("savelog", basicLog);
}
private void fillBasicLog(BasicLogEntity basicLogEntity, JoinPoint jp, HttpServletRequest request, ApiLog log, Method method) {
if(StpUtil.isLogin()){
String idcard= StpUtil.getLoginIdAsString();
if(StringUtils.isNotBlank(idcard)) {
UserEntity userEntity=new UserEntity();
userEntity.setIdCard(idcard);
userEntity=userMapper.selectOne(userEntity);
if (userEntity != null) {
basicLogEntity.setIdCard(idcard);
basicLogEntity.setUsername(userEntity.getUsername());
}
}
}
basicLogEntity.setUserIp(IpUtil.getIpAddr(request));
basicLogEntity.setRequestApi(request.getRequestURI());
basicLogEntity.setRequestMethod(request.getMethod());
basicLogEntity.setMethodName(method.getName());
basicLogEntity.setActionDescribe(log.actionDescrible());
basicLogEntity.setModule(log.module());
basicLogEntity.setLogType(log.type());
basicLogEntity.setClassName(method.getDeclaringClass().getSimpleName());
basicLogEntity.setCreateTime(new Date());
String args = Arrays.toString(jp.getArgs());
basicLogEntity.setRequestArgs(args.length()<=64?args:args.substring(0,64));
}
}
三、日志实体类
@Data
@Component
@Table(name = "xxxxxxxxxxxx")
public class BasicLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
public Integer accountId;
public String idCard;
public String username;
public String userIp;
public String actionDescribe;
public String module;
public String requestApi;
public String requestMethod;
public String className;
public String methodName;
public String requestArgs;
public String exceptionMsg;
public String notice;
public short success;
public Date createTime;
public int logType;
}
四、动态定时器定时将redis中的日志同步到数据库中
@Configuration
@EnableScheduling
@EnableAsync
public class LogDeal implements SchedulingConfigurer {
private static final String SAVE_CONSTANT = "savelog";
@Autowired
public RedisClient redis;
@Autowired
public LogMapper logMapper;
public LogDeal() {
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(() -> {
Boolean res = true;
while(res) {
BasicLogEntity basicLog = (BasicLogEntity) this.redis.listPop("savelog");
if (basicLog != null) {
this.logMapper.saveLog(basicLog);
} else {
res = false;
}
}
}, (triggerContext) -> {
String cron = "0 0/1 * * * ?";
if (!StringUtils.isEmpty(Constant.logsavetime)) {
cron = Constant.logsavetime;
}
return (new CronTrigger(cron)).nextExecutionTime(triggerContext);
});
}
}
五、数据入库Mapper文件
@Mapper
@Component
public interface LogMapper {
@SelectProvider(
type = LogProvider.class,
method = "getTable"
)
String getTable(String tablename);
@SelectProvider(
type = LogProvider.class,
method = "createTable"
)
Boolean createTable(String tablename);
@SelectProvider(
type = LogProvider.class,
method = "getLog"
)
List<BasicLogEntity> getList(Integer type);
@InsertProvider(
type = LogProvider.class,
method = "saveLog"
)
Boolean saveLog(BasicLogEntity log);
public static class LogProvider {
public LogProvider() {
}
public String getLog(Integer type) {
String sql = "select * from ";
String[] tables = Constant.logtable.split(",");
sql = sql + tables[type];
return sql + "order by id desc";
}
public String saveLog(BasicLogEntity log) {
String sql = "insert into ";
String[] tables = Constant.logtable.split(",");
sql = sql + tables[log.logType];
return sql + "(id_card,username,user_ip,action_describe,module,request_api,request_method,class_name,method_name,request_args," + "exception_msg,notice,success,create_time) values(#{idCard},#{username},#{userIp},#{actionDescribe},#{module},#{requestApi}," + "#{requestMethod},#{className},#{methodName},#{requestArgs},#{exceptionMsg},#{notice},#{success},#{createTime})";
}
public String getTable(String tablename) {
String sql = "show tables like '" + tablename + "'";
return sql;
}
public String createTable(String tablename) {
String sql = "CREATE TABLE " + tablename + "(\r\n" + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\r\n" + " `account_id` bigint(20) DEFAULT NULL,\r\n" + " `username` varchar(32) DEFAULT NULL,\r\n" + " `user_ip` varchar(32) DEFAULT NULL,\r\n" + " `action_describe` varchar(32) DEFAULT NULL,\r\n" + " `request_api` varchar(32) DEFAULT NULL,\r\n" + " `request_method` varchar(32) DEFAULT NULL,\r\n" + " `class_name` varchar(32) DEFAULT NULL,\r\n" + " `method_name` varchar(32) DEFAULT NULL,\r\n" + " `request_args` varchar(64) DEFAULT NULL,\r\n" + " `exception_msg` varchar(128) DEFAULT NULL,\r\n" + " `notice` varchar(64) DEFAULT NULL,\r\n" + " `success` tinyint(1) DEFAULT NULL,\r\n" + " `createtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\r\n" + " PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='xxxxxxxxx'";
return sql;
}
}
}
六、其他(读取配置文件参数工具类、IPUtil获取接口请求IP、配置文件参数映射)
public class Constant {
public static final int need_login = 402;
public static final int no_login = Integer.parseInt((String)BaseUtil.getProMap().get("no_login"));
public static final int token_expired = Integer.parseInt((String)BaseUtil.getProMap().get("token_expired"));
public static final int auth_fail = Integer.parseInt((String)BaseUtil.getProMap().get("auth_fail"));
public static final int no_permi = Integer.parseInt((String)BaseUtil.getProMap().get("no_permi"));
public static final String jump = (String)BaseUtil.getProMap().get("jump");
public static final String jumpurl = (String)BaseUtil.getProMap().get("jumpurl");
public static final String type = (String)BaseUtil.getProMap().get("type");
public static final String origin = (String)BaseUtil.getProMap().get("origin");
public static final String method = (String)BaseUtil.getProMap().get("method");
public static final String max_age = (String)BaseUtil.getProMap().get("max_age");
public static final String header = (String)BaseUtil.getProMap().get("header");
public static final String logsavetime = (String)BaseUtil.getProMap().get("logsavetime");
public static final String logtable = (String)BaseUtil.getProMap().get("logtable");
public static final String headername = (String)BaseUtil.getProMap().get("headername");
public static final String jwtkey = (String)BaseUtil.getProMap().get("jwtkey");
public static final String session_id = (String)BaseUtil.getProMap().get("session_id");
public static final String session_timeout = (String)BaseUtil.getProMap().get("session_timeout");
public static final String cook_path = (String)BaseUtil.getProMap().get("cook_path");
public static final String webpath = (String)BaseUtil.getProMap().get("webpath");
public static final String dirpath = (String)BaseUtil.getProMap().get("dirpath");
public static final String http_path = (String)BaseUtil.getProMap().get("http_path");
public static final String valicodemin = (String)BaseUtil.getProMap().get("valicodemin");
public static final String valicodemax = (String)BaseUtil.getProMap().get("valicodemax");
public static final String valicodewidth = (String)BaseUtil.getProMap().get("valicodewidth");
public static final String valicodebit = (String)BaseUtil.getProMap().get("valicodebit");
public static final String redistopic = (String)BaseUtil.getProMap().get("redistopic");
public static final String certpassword = (String)BaseUtil.getProMap().get("certpassword");
public static final String certpath = (String)BaseUtil.getProMap().get("certpath");
public Constant() {
}
}
public class IpUtil {
public IpUtil() {
}
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 ip;
}
}
public class BaseUtil {
private static BaseUtil baseUtil = null;
private static Map<String, String> proMap = new HashMap();
private BaseUtil() {
Properties pro = new Properties();
InputStream iso = this.getClass().getClassLoader().getResourceAsStream("base.properties");
try {
pro.load(iso);
Enumeration e = pro.propertyNames();
while(e.hasMoreElements()) {
String key = (String)e.nextElement();
String value = (String)pro.get(key);
proMap.put(key, value);
}
} catch (IOException var14) {
var14.printStackTrace();
} finally {
try {
iso.close();
} catch (IOException var13) {
var13.printStackTrace();
}
}
}
public static Map<String, String> getProMap() {
if (baseUtil == null) {
baseUtil = new BaseUtil();
}
return proMap;
}
}
#base.properties配置文件
#基础权限
#token过期
token_expired=401
#没有登录
no_login=402
#认证失败
auth_fail=403
#无权限
no_permi=404
#跨域配置
#type==true 允许所有跨域 type==false 只允许配置中的来源
type=true
#允许通过的源 type==0 必须配置,号分隔
origin=
#许通过的请求方法,号分隔
method=POST, GET, OPTIONS, DELETE, PUT, PATCH
#多久免检
max_age=36000
#允许通过的请求头
header=X-Opt-Dept-Id,Content-Type,x-requested-with,token
#日志配置 认证登录日志 申请资源日志 释放资源日志 工单审核日志 权限分配日志 ---表名 对应下表 0 1 2 3 4 5
#logsavetime=0 0/10 * * * ?
logsavetime=0 0/1 * * * ?
logtable=t_log_auth_login,t_log_apply_res,t_log_release_res,t_log_order_review,t_log_allocation_rol
#session配置
session_id=sessionId
#session 失效时间单位是秒
session_timeout=10800
cook_path=/