mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-06 23:21:16 +08:00
功能追加:显示在线人数
This commit is contained in:
@@ -36,6 +36,8 @@ public class CachingConfig {
|
||||
public static final String TAG_CACHE_NAME="openisle_tags";
|
||||
// 分类缓存名
|
||||
public static final String CATEGORY_CACHE_NAME="openisle_categories";
|
||||
// 在线人数缓存名
|
||||
public static final String ONLINE_CACHE_NAME="openisle_online";
|
||||
|
||||
/**
|
||||
* 自定义Redis的序列化器
|
||||
|
||||
@@ -129,6 +129,8 @@ public class SecurityConfig {
|
||||
.requestMatchers(HttpMethod.GET, "/api/sitemap.xml").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/channels").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/rss").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/online/**").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/online/**").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/point-goods").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/point-goods").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/categories/**").hasAuthority("ADMIN")
|
||||
@@ -183,7 +185,8 @@ public class SecurityConfig {
|
||||
}
|
||||
} else if (!uri.startsWith("/api/auth") && !publicGet
|
||||
&& !uri.startsWith("/api/ws") && !uri.startsWith("/api/sockjs")
|
||||
&& !uri.startsWith("/api/v3/api-docs")) {
|
||||
&& !uri.startsWith("/api/v3/api-docs")
|
||||
&& !uri.startsWith("/api/online")) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write("{\"error\": \"Missing token\"}");
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.openisle.controller;
|
||||
|
||||
import com.openisle.config.CachingConfig;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* @author smallclover
|
||||
* @since 2025-09-05
|
||||
* 统计在线人数
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/online")
|
||||
@RequiredArgsConstructor
|
||||
public class OnlineController {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
private static final String ONLINE_KEY = CachingConfig.ONLINE_CACHE_NAME +":";
|
||||
|
||||
@PostMapping("/heartbeat")
|
||||
public void ping(@RequestParam String userId){
|
||||
redisTemplate.opsForValue().set(ONLINE_KEY+userId,"1", Duration.ofSeconds(150));
|
||||
}
|
||||
|
||||
@GetMapping("/count")
|
||||
public long count(){
|
||||
return redisTemplate.keys(ONLINE_KEY+"*").size();
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,12 @@
|
||||
邀请
|
||||
<i v-if="isCopying" class="fas fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<ToolTip v-if="!isMobile" content="当前在线人数" placement="bottom">
|
||||
<div class="online-count">
|
||||
<i class="fas fa-people-group"></i>
|
||||
<span>{{ onlineCount }}</span>
|
||||
</div>
|
||||
</ToolTip>
|
||||
<ToolTip content="复制RSS链接" placement="bottom">
|
||||
<div class="rss-icon" @click="copyRssLink">
|
||||
<i class="fas fa-rss"></i>
|
||||
@@ -115,6 +120,46 @@ const userMenu = ref(null)
|
||||
const menuBtn = ref(null)
|
||||
const isCopying = ref(false)
|
||||
|
||||
const onlineCount = ref(0)
|
||||
|
||||
// 心跳检测
|
||||
async function sendPing() {
|
||||
try {
|
||||
// 已登录就用 userId,否则随机生成游客ID
|
||||
let userId = authState.userId
|
||||
if (userId) {
|
||||
// 用户已登录,清理游客 ID
|
||||
localStorage.removeItem('guestId')
|
||||
} else {
|
||||
// 游客模式
|
||||
let savedId = localStorage.getItem('guestId')
|
||||
if (!savedId) {
|
||||
savedId = `guest-${crypto.randomUUID()}`
|
||||
localStorage.setItem('guestId', savedId)
|
||||
}
|
||||
userId = savedId
|
||||
}
|
||||
const res = await fetch(`${API_BASE_URL}/api/online/heartbeat?userId=${userId}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error("心跳失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取在线人数
|
||||
async function fetchCount() {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/api/online/count`, {
|
||||
method: 'GET',
|
||||
})
|
||||
onlineCount.value = await res.json()
|
||||
} catch (e) {
|
||||
console.error("获取在线人数失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const search = () => {
|
||||
showSearch.value = true
|
||||
nextTick(() => {
|
||||
@@ -262,6 +307,12 @@ onMounted(async () => {
|
||||
await updateUnread()
|
||||
},
|
||||
)
|
||||
|
||||
// 新增的在线人数逻辑
|
||||
sendPing()
|
||||
fetchCount()
|
||||
setInterval(sendPing, 120000) // 每 2 分钟发一次心跳
|
||||
setInterval(fetchCount, 60000) // 每 1 分更新 UI
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -452,6 +503,15 @@ onMounted(async () => {
|
||||
animation: rss-glow 2s 3;
|
||||
}
|
||||
|
||||
.online-count {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: var(--primary-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@keyframes rss-glow {
|
||||
0% {
|
||||
text-shadow: 0 0 0px var(--primary-color);
|
||||
|
||||
Reference in New Issue
Block a user