mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-20 22:11:01 +08:00
用户访问统计使用缓存+定时任务
+ 重要:注释的地方如果没用到@nagisa77可以删除
This commit is contained in:
@@ -44,6 +44,8 @@ public class CachingConfig {
|
||||
public static final String VERIFY_CACHE_NAME="openisle_verify";
|
||||
// 发帖频率限制
|
||||
public static final String LIMIT_CACHE_NAME="openisle_limit";
|
||||
// 用户访问统计
|
||||
public static final String VISIT_CACHE_NAME="openisle_visit";
|
||||
|
||||
/**
|
||||
* 自定义Redis的序列化器
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.openisle.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -26,6 +27,8 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
@@ -44,6 +47,8 @@ public class SecurityConfig {
|
||||
@Value("${app.website-url}")
|
||||
private String websiteUrl;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
@@ -208,7 +213,8 @@ public class SecurityConfig {
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
var auth = org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth != null && auth.isAuthenticated() && !(auth instanceof org.springframework.security.authentication.AnonymousAuthenticationToken)) {
|
||||
userVisitService.recordVisit(auth.getName());
|
||||
String key = CachingConfig.VISIT_CACHE_NAME+":"+ LocalDate.now();
|
||||
redisTemplate.opsForSet().add(key, auth.getName());
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@@ -115,10 +115,10 @@ public class PostController {
|
||||
if (tagId != null) {
|
||||
tids = java.util.List.of(tagId);
|
||||
}
|
||||
|
||||
if (auth != null) {
|
||||
userVisitService.recordVisit(auth.getName());
|
||||
}
|
||||
// 只需要在请求的一开始统计一次
|
||||
// if (auth != null) {
|
||||
// userVisitService.recordVisit(auth.getName());
|
||||
// }
|
||||
|
||||
boolean hasCategories = ids != null && !ids.isEmpty();
|
||||
boolean hasTags = tids != null && !tids.isEmpty();
|
||||
@@ -152,10 +152,10 @@ public class PostController {
|
||||
if (tagId != null) {
|
||||
tids = java.util.List.of(tagId);
|
||||
}
|
||||
|
||||
if (auth != null) {
|
||||
userVisitService.recordVisit(auth.getName());
|
||||
}
|
||||
// 只需要在请求的一开始统计一次
|
||||
// if (auth != null) {
|
||||
// userVisitService.recordVisit(auth.getName());
|
||||
// }
|
||||
|
||||
return postService.listPostsByViews(ids, tids, page, pageSize)
|
||||
.stream().map(postMapper::toSummaryDto).collect(Collectors.toList());
|
||||
@@ -177,10 +177,10 @@ public class PostController {
|
||||
if (tagId != null) {
|
||||
tids = java.util.List.of(tagId);
|
||||
}
|
||||
|
||||
if (auth != null) {
|
||||
userVisitService.recordVisit(auth.getName());
|
||||
}
|
||||
// 只需要在请求的一开始统计一次
|
||||
// if (auth != null) {
|
||||
// userVisitService.recordVisit(auth.getName());
|
||||
// }
|
||||
|
||||
return postService.listPostsByLatestReply(ids, tids, page, pageSize)
|
||||
.stream().map(postMapper::toSummaryDto).collect(Collectors.toList());
|
||||
@@ -202,9 +202,10 @@ public class PostController {
|
||||
if (tagId != null) {
|
||||
tids = java.util.List.of(tagId);
|
||||
}
|
||||
if (auth != null) {
|
||||
userVisitService.recordVisit(auth.getName());
|
||||
}
|
||||
// 只需要在请求的一开始统计一次
|
||||
// if (auth != null) {
|
||||
// userVisitService.recordVisit(auth.getName());
|
||||
// }
|
||||
return postService.listFeaturedPosts(ids, tids, page, pageSize)
|
||||
.stream().map(postMapper::toSummaryDto).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ public class UserController {
|
||||
));
|
||||
}
|
||||
|
||||
// 这个方法似乎没有使用?
|
||||
@PostMapping("/me/signin")
|
||||
public Map<String, Integer> signIn(Authentication auth) {
|
||||
int reward = levelService.awardForSignin(auth.getName());
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.openisle.schdule;
|
||||
|
||||
import com.openisle.config.CachingConfig;
|
||||
import com.openisle.model.User;
|
||||
import com.openisle.model.UserVisit;
|
||||
import com.openisle.repository.UserRepository;
|
||||
import com.openisle.repository.UserVisitRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 执行计划
|
||||
* 将每天用户访问落库
|
||||
* @author smallclover
|
||||
* @since 2025-09-09
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UserVisitScheduler {
|
||||
private final RedisTemplate redisTemplate;
|
||||
private final UserRepository userRepository;
|
||||
private final UserVisitRepository userVisitRepository;
|
||||
|
||||
@Scheduled(cron = "0 5 0 * * ?")// 每天 00:05 执行
|
||||
public void persistDailyVisits(){
|
||||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||||
String key = CachingConfig.VISIT_CACHE_NAME+":"+ yesterday;
|
||||
Set<String> usernames = redisTemplate.opsForSet().members(key);
|
||||
if(!CollectionUtils.isEmpty(usernames)){
|
||||
for(String username: usernames){
|
||||
User user = userRepository.findByUsername(username).orElse(null);
|
||||
if(user != null){
|
||||
UserVisit userVisit = new UserVisit();
|
||||
userVisit.setUser(user);
|
||||
userVisit.setVisitDate(yesterday);
|
||||
userVisitRepository.save(userVisit);
|
||||
}
|
||||
}
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.config.CachingConfig;
|
||||
import com.openisle.model.User;
|
||||
import com.openisle.model.UserVisit;
|
||||
import com.openisle.repository.UserRepository;
|
||||
import com.openisle.repository.UserVisitRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -17,6 +24,8 @@ public class UserVisitService {
|
||||
private final UserVisitRepository userVisitRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
public boolean recordVisit(String username) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||
@@ -30,10 +39,36 @@ public class UserVisitService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计访问次数,改为从缓存获取/数据库获取
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
public long countVisits(String username) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||
return userVisitRepository.countByUser(user);
|
||||
|
||||
// 如果缓存存在就返回
|
||||
String key1 = CachingConfig.VISIT_CACHE_NAME + ":"+LocalDate.now()+":count:"+username;
|
||||
Integer cached = (Integer) redisTemplate.opsForValue().get(key1);
|
||||
if(cached != null){
|
||||
return cached.longValue();
|
||||
}
|
||||
|
||||
// Redis Set 检查今天是否访问
|
||||
String todayKey = CachingConfig.VISIT_CACHE_NAME + ":" + LocalDate.now();
|
||||
boolean todayVisited = redisTemplate.opsForSet().isMember(todayKey, username);
|
||||
|
||||
Long visitCount = userVisitRepository.countByUser(user);
|
||||
if (todayVisited) visitCount += 1;
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59);
|
||||
long secondsUntilEndOfDay = Duration.between(now, endOfDay).getSeconds();
|
||||
|
||||
// 写入缓存,设置 TTL,当天剩余时间
|
||||
redisTemplate.opsForValue().set(key1, visitCount, Duration.ofSeconds(secondsUntilEndOfDay));
|
||||
return visitCount;
|
||||
}
|
||||
|
||||
public long countDau(LocalDate date) {
|
||||
|
||||
Reference in New Issue
Block a user