自定义注解+AOP,实现日志拦截存储

本文介绍了一种使用自定义注解和AOP技术实现日志存储的方法。通过定义注解OperateLog并结合AOP切面,可以自动记录请求模块、业务方法、操作人员类型等信息,并在方法执行前后进行日志记录。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里我们介绍下使用自定义注解,加aop来实现日志的存储

首先自定义注解

import com.etc.mainboot.enums.BusinessType;
import com.etc.mainboot.enums.OperatorType;

import java.lang.annotation.*;

/**
 * 操作日志记录处理
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {

    /**
     * 请求模块-title
     */
    String title() default "";

    /**
     * 请求具体业务方法
     */
    BusinessType bizType() default BusinessType.OTHER;

    /**
     * 操作人员类型
     */
    OperatorType operateType() default OperatorType.OTHER;

    /**
     * 是否保存请求参数,默认false
     */
    boolean isSaveData() default false;
}

编写AOP切面

package com.etc.mainboot.aop;

import com.etc.mainboot.annotation.OperateLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * aop切面处理
 * 对模块、方法、参数的日志处理
 * @Aspect注解切面,@PointCut定义切点,标记方法
 * @author ChenDang
 * @date 2019/5/28 0028
 */
@Aspect
@Component
public class LogAspect {

    /**
     * 此处定义切点是注解方法,这里使用注解的方式。
     * 也可以使用aop最原始的支撑包名的方式
     */
    @Pointcut("@annotation(com.etc.mainboot.annotation.OperateLog)")
    public void operateLog(){}

    /**
     * 环绕增强,在这里进行日志操作
     */
    @Around("operateLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{

        Object res = null;
        long time = System.currentTimeMillis();
        try {
            //方法继续执行
            res =  joinPoint.proceed();
            time = System.currentTimeMillis() - time;
            return res;
        } finally {
            try {
                //方法执行完成后增加日志
                addOperationLog(joinPoint,res,time);
            }catch (Exception e){
                System.out.println("LogAspect 操作失败:" + e.getMessage());
                e.printStackTrace();
            }
        }
    }


    private void addOperationLog(JoinPoint joinPoint, Object res, long time){
        //joinPoint.getSignature()获取封装了署名的对象,在该对象中可以获取目标方法名,
        //所属类的class等信息
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String method = signature.getDeclaringTypeName() + "." + signature.getName();
        System.out.println("拦截方法名:"+method);
        //获取注解对象,取到注解里定义的字段
        OperateLog annotation = signature.getMethod().getAnnotation(OperateLog.class);
        if(annotation != null) {

            String title = annotation.title();
            String bizType = annotation.bizType().name();
            String operateType = annotation.operateType().name();
            boolean isSaveData = annotation.isSaveData();
            System.out.println("title:"+title+",bizType:"+bizType+",operateType:"+operateType+",isSaveData:"+isSaveData);
            //获取参数
            HttpServletRequest request = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest();
            Map<String, String[]> map =request.getParameterMap();
            if (map == null || map.isEmpty()) {
                return;
            }
            //输出参数
            for(String key : map.keySet()){
                System.out.println("key:"+key+",value="+map.get(key));
            }
        }
        //TODO 这里保存日志
        System.out.println("记录日志:");
    }

    @Before("operateLog()")
    public void doBeforeAdvice(JoinPoint joinPoint){
        System.out.println("进入方法前执行.....");

    }

    /**
     * 处理完请求,返回内容
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "operateLog()")
    public void doAfterReturning(Object ret) {
        System.out.println("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     */
    @AfterThrowing("operateLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }


    /**
     * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
     */
    @After("operateLog()")
    public void after(JoinPoint jp){
        System.out.println("方法最后执行.....");
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private OperateLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(OperateLog.class);
        }
        return null;
    }

}

编写Controller,测试

package com.etc.mainboot;

import com.etc.mainboot.annotation.OperateLog;
import com.etc.mainboot.enums.BusinessType;
import com.etc.mainboot.enums.OperatorType;
import com.etc.mainboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/index")
public class IndexController {

    @Autowired
    UserService userService;

    @OperateLog(title = "hello",bizType = BusinessType.QUERY,operateType = OperatorType.CUSTOMER)
    @RequestMapping("/hello")
    public String hello(){
        userService.hello();
        return "hello world";
    }
}

编写Service和实现类

public interface UserService {

    public void hello();
}
@Service
public class UserServiceImpl implements UserService {

    @Async("asyncServiceExecutor")
    @Override
    public void hello(){
        System.out.println("当前运行的线程名称:" + Thread.currentThread().getName());
    }
}

这里我们使用了线程池管理

@Configuration
@EnableAsync
public class ThreadExecutorConfig {

    @Bean
    public Executor asyncServiceExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(10);
        //配置最大线程数
        executor.setMaxPoolSize(10);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-thread-");

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}

访问controller

看下控制台输出结果

==========================================

进入方法前执行.....
拦截方法名:com.etc.mainboot.IndexController.hello
当前运行的线程名称:async-service-thread-3
title:hello,bizType:QUERY,operateType:CUSTOMER,isSaveData:false
key:username,value=[Ljava.lang.String;@63cf387f
记录日志:
方法最后执行.....
方法的返回值 : hello world 

 ===================================

总结

使用自定义注解+aop,实现日志的存储。在环绕通知doAround里,我们可以获取访问方法和注解相关信息,以及request请求里的信息,包含参数、参数值、请求方法、客户端信息等等,然后在doAround里,保存信息到数据库,这里我就没有写数据库操作了,大家自己写下。

 

<think>好的,我需要解决用户的问题,即如何构建一个基于自定义注解AOP、Kafka、Flink和预测模型的用户行为处理流水线。首先,我得理清楚整个流程的各个环节,并确保每一步都能有效衔接。 首先,用户提到要用自定义注解AOP拦截用户行为,发送到Kafka。这部分我需要考虑如何设计注解,以及在哪些方法上应用这些注解来捕获用户行为。AOP实现可能需要使用Spring AOP或者AspectJ,需要根据项目实际情况选择。然后,拦截到的数据如何序列化并发送到Kafka,这里要确保消息的可靠性和高效性。 接下来是Flink消费Kafka数据,计算实时特征。这里需要考虑Flink的流处理能力,如何设置Kafka消费者,处理时间窗口,状态管理,以及如何将计算后的特征与历史数据结合。可能需要使用Flink的窗口函数和状态后端,比如RocksDB或者FsStateBackend。 然后是预测模型部分,需要实时特征和历史属性输入,计算出用户偏好。这里可能需要一个实时推理服务,比如使用TensorFlow Serving或者PyTorch的TorchServe。同时,历史属性可能需要从数据库或缓存中获取,需要考虑数据的一致性和延迟问题。 最后,将预测结果推送回系统,可能需要通过消息队列或者直接写入数据库,供其他服务调用。需要考虑系统的实时性和可靠性,确保结果能够及时更新并传递到前端或相关服务。 在整个过程中,还需要考虑各个组件的高可用性、容错性、扩展性,以及监控和日志记录,确保系统稳定运行。同时,数据格式的统一和序列化方式(如JSON、Avro)也需要提前设计,避免上下游解析出错。 现在,我需要分步骤详细说明每个部分的实现方案,确保用户能够根据指导一步步搭建整个流水线。要注意技术选型的合理性,并提供可能的代码示例或配置建议。此外,可能还需要考虑性能优化点,比如Kafka的分区策略、Flink的并行度设置,以及模型推理的批处理等。 最后,验证整个流程的各个环节是否正确,比如通过日志确认AOP拦截是否成功,Kafka消息是否正常生产和消费,Flink作业是否正确处理数据,模型预测结果是否准确,以及推送系统是否接收到结果。测试环节需要涵盖单元测试、集成测试和端到端测试,确保系统稳定可靠。</think>### 分步解决方案 #### 一、用户行为数据采集(注解+AOP+Kafka) 1. **定义用户行为注解** ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UserBehaviorTrack { String eventType(); // 事件类型(如click、view) String description() default ""; } ``` 2. **AOP切面拦截注解** ```java @Aspect @Component public class UserBehaviorAspect { @Autowired private KafkaTemplate<String, String> kafkaTemplate; @Around("@annotation(track)") public Object trackBehavior(ProceedingJoinPoint joinPoint, UserBehaviorTrack track) throws Throwable { // 1. 获取基础信息 Map<String, Object> event = new HashMap<>(); event.put("userId", SecurityContext.getCurrentUserId()); event.put("eventTime", System.currentTimeMillis()); event.put("eventType", track.eventType()); // 2. 解析方法参数(可选) Object[] args = joinPoint.getArgs(); // 可添加参数提取逻辑,如商品ID、页面路径等 // 3. 发送到Kafka kafkaTemplate.send("user_behavior", JSON.toJSONString(event)); // 4. 执行原方法 return joinPoint.proceed(); } } ``` 3. **Kafka配置优化** ```yaml spring.kafka: bootstrap-servers: kafka1:9092,kafka2:9092 producer: acks: all # 确保消息可靠投递 retries: 3 ``` #### 二、实时特征计算(Flink) 1. **Flink消费Kafka数据** ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); Properties properties = new Properties(); properties.setProperty("bootstrap.servers", "kafka1:9092"); properties.setProperty("group.id", "user_behavior_group"); DataStream<String> stream = env.addSource( new FlinkKafkaConsumer<>("user_behavior", new SimpleStringSchema(), properties ) ).setParallelism(2); ``` 2. **特征计算逻辑(示例:5分钟点击率)** ```java stream .map(json -> JSON.parseObject(json, UserBehaviorEvent.class)) .keyBy(UserBehaviorEvent::getUserId) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .aggregate(new AggregateFunction<UserBehaviorEvent, ClickCountAccumulator, Double>() { // 实现累计点击次数 public ClickCountAccumulator createAccumulator() { return new ClickCountAccumulator(); } public ClickCountAccumulator add(UserBehaviorEvent event, ClickCountAccumulator acc) { if ("click".equals(event.getEventType())) { acc.totalClicks++; } acc.totalEvents++; return acc; } public Double getResult(ClickCountAccumulator acc) { return (double) acc.totalClicks / acc.totalEvents; } // ... merge等其他方法 }) .addSink(new RedisSink()); // 将特征写入Redis ``` #### 三、模型预测与推送 1. **特征拼接服务** ```python # 伪代码:实时特征 + 历史特征 def get_user_features(user_id): # 从Redis获取实时特征(Flink计算结果) realtime_feats = redis.get(f"user:{user_id}:realtime") # 从HBase获取历史画像 history_feats = hbase.get_row("user_profile", user_id) return merge_features(realtime_feats, history_feats) ``` 2. **实时模型推理** ```python # 使用TensorFlow Serving HTTP API def predict_preference(user_features): response = requests.post( "http://tf-serving:8501/v1/models/user_model:predict", json={"instances": [user_features]} ) return response.json()["predictions"][0] ``` 3. **结果推送系统** - **推送方式选择**: - 实时推荐场景:通过WebSocket推送至前端 - 后台系统使用:写入Kafka新主题(如 `user_preference_updates`) - **数据格式示例**: ```json { "userId": 12345, "timestamp": 1678904567890, "preferences": { "category": "electronics", "score": 0.92 } } ``` #### 四、关键优化点 1. **数据一致性保障** - Kafka启用事务保证Exactly-Once语义 - Flink Checkpoint机制(间隔30秒) ```java env.enableCheckpointing(30000); env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); ``` 2. **性能优化** - Flink反压处理:`env.setBufferTimeout(10);` - 模型批预测:在Flink中攒批处理(每100条请求一次模型服务) 3. **监控告警** - Kafka监控:Lag突增报警(Prometheus + AlertManager) - Flink Metrics:跟踪窗口处理延迟(Grafana展示) #### 五、验证流程 1. **端到端测试** ```shell # 模拟用户行为 curl -X POST http://app/api/click?itemId=67890 # 查看Kafka消息 kafka-console-consumer --bootstrap-server kafka1:9092 --topic user_behavior # 检查Redis特征 redis-cli HGETALL "user:12345:realtime" # 验证推送结果 kafka-console-consumer --topic user_preference_updates ``` 2. **压测方案** - 使用JMeter模拟1000QPS用户行为 - 观察Flink Checkpoint是否稳定 - 监控模型服务GPU利用率 #### 附:架构图 ``` 用户请求 → Spring应用 → AOP拦截 → Kafka → Flink计算 → Redis特征存储 ↑ ↓ 历史数据(HBase) → 特征拼接 → 模型推理 → Kafka推送 → 业务系统 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值