From fb2bc4252d168c80bed6d9eb5dac00444a86d540 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:04:17 +0800 Subject: [PATCH] refactor: add parameter verification to the visit counter API (#3546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.4.x #### What this PR does / why we need it: 对访问量统计的 API 增加数据合法性校验 点赞同理 see #3530 for more details how to test it? 1. 访问文章和页面可以统计访问量 2. 使用访问以下 API 添加模拟使用错误数据不会在 Counter 模型添加记录 ```shell curl 'http://localhost:8090/apis/api.halo.run/v1alpha1/trackers/counter' -u 'your-username:your-password' \ --header 'Content-Type: application/json' \ --data '{ "group": "fake.halo.run", "plural": "posts", "name": "fake-name", "hostname": "localhost", "screen": "1920x1080", "language": "zh-CN", "url": "/archives/hello-halo", "referrer": "http://localhost:8090/" }' ``` 期望出现日志: ``` 2023-03-21T12:37:08.391+08:00 DEBUG 7036 --- [task-4] r.h.app.metrics.VisitedEventReconciler : Skip visit event for: GroupPluralName[group=fake.halo.run, plural=posts, name=fake-name] ``` 并且 ``` curl 'http://localhost:8090/apis/metrics.halo.run/v1alpha1/counters' -u 'your-username:your-password' ``` 不会出现上述错误数据的记录 #### Which issue(s) this PR fixes: Fixes #3530 #### Does this PR introduce a user-facing change? ```release-note 对访问量统计的 API 增加数据合法性校验 ``` --- .../app/metrics/VisitedEventReconciler.java | 70 ++++++++++++++----- .../app/metrics/VotedEventReconciler.java | 50 +++++++++++-- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/application/src/main/java/run/halo/app/metrics/VisitedEventReconciler.java b/application/src/main/java/run/halo/app/metrics/VisitedEventReconciler.java index 3e11bf7b0..968777a2c 100644 --- a/application/src/main/java/run/halo/app/metrics/VisitedEventReconciler.java +++ b/application/src/main/java/run/halo/app/metrics/VisitedEventReconciler.java @@ -4,16 +4,22 @@ import java.time.Duration; import java.time.Instant; import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.context.SmartLifecycle; import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import run.halo.app.core.extension.Counter; import run.halo.app.event.post.VisitedEvent; import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.GroupVersionKind; +import run.halo.app.extension.Scheme; +import run.halo.app.extension.SchemeManager; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.DefaultController; @@ -88,11 +94,6 @@ public class VisitedEventReconciler Duration.ofMinutes(5)); } - @EventListener(VisitedEvent.class) - public void handlePostPublished(VisitedEvent visitedEvent) { - mergeVisits(visitedEvent); - } - @Override public void start() { this.visitedEventController.start(); @@ -121,18 +122,53 @@ public class VisitedEventReconciler return this.running; } - private void mergeVisits(VisitedEvent event) { - String counterName = - MeterUtils.nameOf(event.getGroup(), event.getPlural(), event.getName()); - pooledVisitsMap.compute(counterName, (name, visits) -> { - if (visits == null) { - return 1; - } else { - return visits + 1; - } - }); - } - public record VisitCountBucket(String name, int visits) { } + + @Component + @RequiredArgsConstructor + public class VisitedEventListener { + private final SchemeManager schemeManager; + + @Async + @EventListener(VisitedEvent.class) + public void onVisited(VisitedEvent visitedEvent) { + mergeVisits(visitedEvent); + } + + private void mergeVisits(VisitedEvent event) { + var gpn = new GroupPluralName(event.getGroup(), event.getPlural(), event.getName()); + if (!checkVisitSubject(gpn)) { + log.debug("Skip visit event for: {}", gpn); + return; + } + String counterName = + MeterUtils.nameOf(event.getGroup(), event.getPlural(), event.getName()); + pooledVisitsMap.compute(counterName, (name, visits) -> { + if (visits == null) { + return 1; + } else { + return visits + 1; + } + }); + } + + private boolean checkVisitSubject(GroupPluralName groupPluralName) { + Optional schemeOptional = schemeManager.schemes().stream() + .filter(scheme -> { + GroupVersionKind gvk = scheme.groupVersionKind(); + return scheme.plural().equals(groupPluralName.plural()) + && gvk.group().equals(groupPluralName.group()); + }) + .findFirst(); + return schemeOptional.map( + scheme -> client.fetch(scheme.groupVersionKind(), groupPluralName.name()) + .isPresent() + ) + .orElse(false); + } + + record GroupPluralName(String group, String plural, String name) { + } + } } diff --git a/application/src/main/java/run/halo/app/metrics/VotedEventReconciler.java b/application/src/main/java/run/halo/app/metrics/VotedEventReconciler.java index 244b3bc11..915d498f4 100644 --- a/application/src/main/java/run/halo/app/metrics/VotedEventReconciler.java +++ b/application/src/main/java/run/halo/app/metrics/VotedEventReconciler.java @@ -2,16 +2,22 @@ package run.halo.app.metrics; import java.time.Duration; import java.time.Instant; +import java.util.Optional; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.context.SmartLifecycle; import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import run.halo.app.core.extension.Counter; import run.halo.app.event.post.DownvotedEvent; import run.halo.app.event.post.UpvotedEvent; import run.halo.app.event.post.VotedEvent; import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.GroupVersionKind; +import run.halo.app.extension.Scheme; +import run.halo.app.extension.SchemeManager; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.DefaultController; @@ -77,11 +83,6 @@ public class VotedEventReconciler implements Reconciler, SmartLifecy Duration.ofMinutes(5)); } - @EventListener(VotedEvent.class) - public void handlePostPublished(VotedEvent votedEvent) { - votedEventQueue.addImmediately(votedEvent); - } - @Override public void start() { this.votedEventController.start(); @@ -98,4 +99,43 @@ public class VotedEventReconciler implements Reconciler, SmartLifecy public boolean isRunning() { return this.running; } + + @Component + @RequiredArgsConstructor + public class VotedEventListener { + private final SchemeManager schemeManager; + + /** + * Add up/down vote event to queue. + */ + @Async + @EventListener(VotedEvent.class) + public void onVoted(VotedEvent event) { + var gpn = new GroupPluralName(event.getGroup(), event.getPlural(), event.getName()); + if (!checkSubject(gpn)) { + log.debug("Skip voted event for: {}", gpn); + return; + } + votedEventQueue.addImmediately(event); + } + + private boolean checkSubject( + GroupPluralName groupPluralName) { + Optional schemeOptional = schemeManager.schemes().stream() + .filter(scheme -> { + GroupVersionKind gvk = scheme.groupVersionKind(); + return scheme.plural().equals(groupPluralName.plural()) + && gvk.group().equals(groupPluralName.group()); + }) + .findFirst(); + return schemeOptional.map( + scheme -> client.fetch(scheme.groupVersionKind(), groupPluralName.name()) + .isPresent() + ) + .orElse(false); + } + + record GroupPluralName(String group, String plural, String name) { + } + } }