mirror of https://github.com/halo-dev/halo
refactor: add parameter verification to the visit counter API (#3546)
#### 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 增加数据合法性校验 ```pull/3561/head
parent
fbe8e627e8
commit
fb2bc4252d
|
@ -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<Scheme> 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<VotedEvent>, 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<VotedEvent>, 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<Scheme> 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue