仪表板功能完善
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package com.emotion.service.impl;
|
package com.emotion.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.emotion.dto.response.DashboardStatsResponse;
|
import com.emotion.dto.response.DashboardStatsResponse;
|
||||||
|
import com.emotion.entity.*;
|
||||||
import com.emotion.service.*;
|
import com.emotion.service.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -84,14 +86,25 @@ public class DashboardServiceImpl implements DashboardService {
|
|||||||
public DashboardStatsResponse.UserStats getUserStats() {
|
public DashboardStatsResponse.UserStats getUserStats() {
|
||||||
try {
|
try {
|
||||||
// 获取用户统计数据
|
// 获取用户统计数据
|
||||||
Long totalUsers = userService.count();
|
Long totalUsers = userService.count(new LambdaQueryWrapper<User>()
|
||||||
Long guestUsers = guestUserService.count();
|
.eq(User::getIsDeleted, 0));
|
||||||
|
|
||||||
// 获取今日新增用户(这里简化处理,实际可能需要根据创建时间查询)
|
Long guestUsers = guestUserService.count(new LambdaQueryWrapper<GuestUser>()
|
||||||
Long todayNewUsers = 0L; // 可以通过查询今日创建的用户数量来获取
|
.eq(GuestUser::getIsDeleted, 0));
|
||||||
|
|
||||||
// 活跃用户数(这里简化处理,可以根据最近登录时间或活动时间来计算)
|
// 获取今日新增用户
|
||||||
Long activeUsers = totalUsers; // 简化处理
|
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||||
|
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||||
|
Long todayNewUsers = userService.count(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getIsDeleted, 0)
|
||||||
|
.ge(User::getCreateTime, todayStart)
|
||||||
|
.lt(User::getCreateTime, todayEnd));
|
||||||
|
|
||||||
|
// 活跃用户数(最近7天有活动的用户)
|
||||||
|
LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7);
|
||||||
|
Long activeUsers = userService.count(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getIsDeleted, 0)
|
||||||
|
.ge(User::getLastActiveTime, sevenDaysAgo));
|
||||||
|
|
||||||
return DashboardStatsResponse.UserStats.builder()
|
return DashboardStatsResponse.UserStats.builder()
|
||||||
.totalUsers(totalUsers)
|
.totalUsers(totalUsers)
|
||||||
@@ -113,11 +126,20 @@ public class DashboardServiceImpl implements DashboardService {
|
|||||||
@Override
|
@Override
|
||||||
public DashboardStatsResponse.ContentStats getContentStats() {
|
public DashboardStatsResponse.ContentStats getContentStats() {
|
||||||
try {
|
try {
|
||||||
Long totalConversations = conversationService.count();
|
Long totalConversations = conversationService.count(new LambdaQueryWrapper<Conversation>()
|
||||||
Long totalMessages = messageService.count();
|
.eq(Conversation::getIsDeleted, 0));
|
||||||
Long diaryPosts = diaryPostService.count();
|
|
||||||
Long communityPosts = communityPostService.count();
|
Long totalMessages = messageService.count(new LambdaQueryWrapper<Message>()
|
||||||
Long emotionRecords = emotionRecordService.count();
|
.eq(Message::getIsDeleted, 0));
|
||||||
|
|
||||||
|
Long diaryPosts = diaryPostService.count(new LambdaQueryWrapper<DiaryPost>()
|
||||||
|
.eq(DiaryPost::getIsDeleted, 0));
|
||||||
|
|
||||||
|
Long communityPosts = communityPostService.count(new LambdaQueryWrapper<CommunityPost>()
|
||||||
|
.eq(CommunityPost::getIsDeleted, 0));
|
||||||
|
|
||||||
|
Long emotionRecords = emotionRecordService.count(new LambdaQueryWrapper<EmotionRecord>()
|
||||||
|
.eq(EmotionRecord::getIsDeleted, 0));
|
||||||
|
|
||||||
return DashboardStatsResponse.ContentStats.builder()
|
return DashboardStatsResponse.ContentStats.builder()
|
||||||
.totalConversations(totalConversations)
|
.totalConversations(totalConversations)
|
||||||
@@ -141,18 +163,40 @@ public class DashboardServiceImpl implements DashboardService {
|
|||||||
@Override
|
@Override
|
||||||
public DashboardStatsResponse.AiServiceStats getAiServiceStats() {
|
public DashboardStatsResponse.AiServiceStats getAiServiceStats() {
|
||||||
try {
|
try {
|
||||||
Long totalApiCalls = cozeApiCallService.count();
|
Long totalApiCalls = cozeApiCallService.count(new LambdaQueryWrapper<CozeApiCall>()
|
||||||
Long aiConfigCount = aiConfigService.count();
|
.eq(CozeApiCall::getIsDeleted, 0));
|
||||||
|
|
||||||
// 获取今日API调用次数(简化处理)
|
Long aiConfigCount = aiConfigService.count(new LambdaQueryWrapper<AiConfig>()
|
||||||
Long todayApiCalls = 0L; // 可以通过查询今日的API调用记录来获取
|
.eq(AiConfig::getIsDeleted, 0));
|
||||||
|
|
||||||
// 获取成功和失败的调用次数(简化处理)
|
// 获取今日API调用次数
|
||||||
Long successfulCalls = totalApiCalls; // 可以通过状态字段查询
|
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||||
Long failedCalls = 0L;
|
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||||
|
Long todayApiCalls = cozeApiCallService.count(new LambdaQueryWrapper<CozeApiCall>()
|
||||||
|
.eq(CozeApiCall::getIsDeleted, 0)
|
||||||
|
.ge(CozeApiCall::getCreateTime, todayStart)
|
||||||
|
.lt(CozeApiCall::getCreateTime, todayEnd));
|
||||||
|
|
||||||
// 平均响应时间(简化处理)
|
// 获取成功和失败的调用次数
|
||||||
Double avgResponseTime = 500.0; // 可以通过计算duration_ms字段的平均值来获取
|
Long successfulCalls = cozeApiCallService.count(new LambdaQueryWrapper<CozeApiCall>()
|
||||||
|
.eq(CozeApiCall::getIsDeleted, 0)
|
||||||
|
.eq(CozeApiCall::getStatus, "success"));
|
||||||
|
|
||||||
|
Long failedCalls = cozeApiCallService.count(new LambdaQueryWrapper<CozeApiCall>()
|
||||||
|
.eq(CozeApiCall::getIsDeleted, 0)
|
||||||
|
.eq(CozeApiCall::getStatus, "failed"));
|
||||||
|
|
||||||
|
// 计算平均响应时间(从duration_ms字段)
|
||||||
|
List<CozeApiCall> apiCalls = cozeApiCallService.list(new LambdaQueryWrapper<CozeApiCall>()
|
||||||
|
.eq(CozeApiCall::getIsDeleted, 0)
|
||||||
|
.isNotNull(CozeApiCall::getDurationMs)
|
||||||
|
.last("LIMIT 1000")); // 取最近1000条记录计算平均值
|
||||||
|
|
||||||
|
Double avgResponseTime = apiCalls.stream()
|
||||||
|
.filter(call -> call.getDurationMs() != null)
|
||||||
|
.mapToInt(CozeApiCall::getDurationMs)
|
||||||
|
.average()
|
||||||
|
.orElse(0.0);
|
||||||
|
|
||||||
return DashboardStatsResponse.AiServiceStats.builder()
|
return DashboardStatsResponse.AiServiceStats.builder()
|
||||||
.totalApiCalls(totalApiCalls)
|
.totalApiCalls(totalApiCalls)
|
||||||
@@ -178,9 +222,15 @@ public class DashboardServiceImpl implements DashboardService {
|
|||||||
@Override
|
@Override
|
||||||
public DashboardStatsResponse.SystemStats getSystemStats() {
|
public DashboardStatsResponse.SystemStats getSystemStats() {
|
||||||
try {
|
try {
|
||||||
Long adminCount = adminService.count();
|
Long adminCount = adminService.count(new LambdaQueryWrapper<Admin>()
|
||||||
Long achievementCount = achievementService.count();
|
.eq(Admin::getIsDeleted, 0)
|
||||||
Long rewardCount = rewardService.count();
|
.eq(Admin::getStatus, 1)); // 只统计正常状态的管理员
|
||||||
|
|
||||||
|
Long achievementCount = achievementService.count(new LambdaQueryWrapper<Achievement>()
|
||||||
|
.eq(Achievement::getIsDeleted, 0));
|
||||||
|
|
||||||
|
Long rewardCount = rewardService.count(new LambdaQueryWrapper<Reward>()
|
||||||
|
.eq(Reward::getIsDeleted, 0));
|
||||||
|
|
||||||
// 获取系统运行时间
|
// 获取系统运行时间
|
||||||
long uptimeMs = ManagementFactory.getRuntimeMXBean().getUptime();
|
long uptimeMs = ManagementFactory.getRuntimeMXBean().getUptime();
|
||||||
@@ -210,27 +260,70 @@ public class DashboardServiceImpl implements DashboardService {
|
|||||||
List<DashboardStatsResponse.RecentActivity> activities = new ArrayList<>();
|
List<DashboardStatsResponse.RecentActivity> activities = new ArrayList<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 这里可以添加获取最近活动的逻辑
|
// 获取最近注册的用户
|
||||||
// 例如:最近的用户注册、消息发送、帖子创建等
|
List<User> recentUsers = userService.list(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getIsDeleted, 0)
|
||||||
|
.orderByDesc(User::getCreateTime)
|
||||||
|
.last("LIMIT 3"));
|
||||||
|
|
||||||
// 示例活动
|
for (User user : recentUsers) {
|
||||||
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
||||||
.type("user_register")
|
.type("user_register")
|
||||||
.description("新用户注册")
|
.description("新用户注册: " + (user.getNickname() != null ? user.getNickname() : user.getUsername()))
|
||||||
.userId("system")
|
.userId(user.getId())
|
||||||
.username("系统")
|
.username(user.getNickname() != null ? user.getNickname() : user.getUsername())
|
||||||
.activityTime(LocalDateTime.now().minusMinutes(30))
|
.activityTime(user.getCreateTime())
|
||||||
.extraData(new HashMap<>())
|
.extraData(new HashMap<>())
|
||||||
.build());
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最近的对话
|
||||||
|
List<Conversation> recentConversations = conversationService.list(new LambdaQueryWrapper<Conversation>()
|
||||||
|
.eq(Conversation::getIsDeleted, 0)
|
||||||
|
.orderByDesc(Conversation::getCreateTime)
|
||||||
|
.last("LIMIT 3"));
|
||||||
|
|
||||||
|
for (Conversation conversation : recentConversations) {
|
||||||
|
Map<String, Object> extraData = new HashMap<>();
|
||||||
|
extraData.put("conversationId", conversation.getId());
|
||||||
|
extraData.put("title", conversation.getTitle());
|
||||||
|
|
||||||
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
||||||
.type("ai_chat")
|
.type("ai_chat")
|
||||||
.description("AI聊天对话")
|
.description("新的AI对话: " + (conversation.getTitle() != null ? conversation.getTitle() : "未命名对话"))
|
||||||
.userId("system")
|
.userId(conversation.getUserId())
|
||||||
.username("系统")
|
.username("用户")
|
||||||
.activityTime(LocalDateTime.now().minusMinutes(15))
|
.activityTime(conversation.getCreateTime())
|
||||||
.extraData(new HashMap<>())
|
.extraData(extraData)
|
||||||
.build());
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最近的日记帖子
|
||||||
|
List<DiaryPost> recentDiaryPosts = diaryPostService.list(new LambdaQueryWrapper<DiaryPost>()
|
||||||
|
.eq(DiaryPost::getIsDeleted, 0)
|
||||||
|
.orderByDesc(DiaryPost::getCreateTime)
|
||||||
|
.last("LIMIT 2"));
|
||||||
|
|
||||||
|
for (DiaryPost post : recentDiaryPosts) {
|
||||||
|
Map<String, Object> extraData = new HashMap<>();
|
||||||
|
extraData.put("postId", post.getId());
|
||||||
|
extraData.put("title", post.getTitle());
|
||||||
|
|
||||||
|
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
||||||
|
.type("diary_post")
|
||||||
|
.description("新的日记: " + (post.getTitle() != null ? post.getTitle() : "无标题"))
|
||||||
|
.userId(post.getUserId())
|
||||||
|
.username("用户")
|
||||||
|
.activityTime(post.getCreateTime())
|
||||||
|
.extraData(extraData)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间排序,取最新的10条
|
||||||
|
activities.sort((a, b) -> b.getActivityTime().compareTo(a.getActivityTime()));
|
||||||
|
if (activities.size() > 10) {
|
||||||
|
activities = activities.subList(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取最近活动失败", e);
|
log.error("获取最近活动失败", e);
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.emotion.service;
|
||||||
|
|
||||||
|
import com.emotion.dto.response.DashboardStatsResponse;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仪表盘服务测试类
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2025-10-31
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("local")
|
||||||
|
public class DashboardServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DashboardService dashboardService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDashboardStats() {
|
||||||
|
// 测试获取仪表盘统计数据
|
||||||
|
DashboardStatsResponse stats = dashboardService.getDashboardStats();
|
||||||
|
|
||||||
|
assertNotNull(stats, "仪表盘统计数据不应为空");
|
||||||
|
assertNotNull(stats.getUserStats(), "用户统计数据不应为空");
|
||||||
|
assertNotNull(stats.getContentStats(), "内容统计数据不应为空");
|
||||||
|
assertNotNull(stats.getAiServiceStats(), "AI服务统计数据不应为空");
|
||||||
|
assertNotNull(stats.getSystemStats(), "系统统计数据不应为空");
|
||||||
|
assertNotNull(stats.getUpdateTime(), "更新时间不应为空");
|
||||||
|
|
||||||
|
// 验证用户统计数据
|
||||||
|
DashboardStatsResponse.UserStats userStats = stats.getUserStats();
|
||||||
|
assertTrue(userStats.getTotalUsers() >= 0, "总用户数应大于等于0");
|
||||||
|
assertTrue(userStats.getTodayNewUsers() >= 0, "今日新增用户数应大于等于0");
|
||||||
|
assertTrue(userStats.getActiveUsers() >= 0, "活跃用户数应大于等于0");
|
||||||
|
assertTrue(userStats.getGuestUsers() >= 0, "访客用户数应大于等于0");
|
||||||
|
|
||||||
|
// 验证内容统计数据
|
||||||
|
DashboardStatsResponse.ContentStats contentStats = stats.getContentStats();
|
||||||
|
assertTrue(contentStats.getTotalConversations() >= 0, "总对话数应大于等于0");
|
||||||
|
assertTrue(contentStats.getTotalMessages() >= 0, "总消息数应大于等于0");
|
||||||
|
assertTrue(contentStats.getDiaryPosts() >= 0, "日记帖子数应大于等于0");
|
||||||
|
assertTrue(contentStats.getCommunityPosts() >= 0, "社区帖子数应大于等于0");
|
||||||
|
assertTrue(contentStats.getEmotionRecords() >= 0, "情绪记录数应大于等于0");
|
||||||
|
|
||||||
|
// 验证AI服务统计数据
|
||||||
|
DashboardStatsResponse.AiServiceStats aiStats = stats.getAiServiceStats();
|
||||||
|
assertTrue(aiStats.getTotalApiCalls() >= 0, "总API调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getTodayApiCalls() >= 0, "今日API调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getSuccessfulCalls() >= 0, "成功调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getFailedCalls() >= 0, "失败调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getAvgResponseTime() >= 0, "平均响应时间应大于等于0");
|
||||||
|
assertTrue(aiStats.getAiConfigCount() >= 0, "AI配置数量应大于等于0");
|
||||||
|
|
||||||
|
// 验证系统统计数据
|
||||||
|
DashboardStatsResponse.SystemStats systemStats = stats.getSystemStats();
|
||||||
|
assertTrue(systemStats.getAdminCount() >= 0, "管理员数量应大于等于0");
|
||||||
|
assertTrue(systemStats.getAchievementCount() >= 0, "成就数量应大于等于0");
|
||||||
|
assertTrue(systemStats.getRewardCount() >= 0, "奖励数量应大于等于0");
|
||||||
|
assertNotNull(systemStats.getUptime(), "系统运行时间不应为空");
|
||||||
|
|
||||||
|
System.out.println("=== 仪表盘统计数据测试结果 ===");
|
||||||
|
System.out.println("总用户数: " + userStats.getTotalUsers());
|
||||||
|
System.out.println("今日新增用户: " + userStats.getTodayNewUsers());
|
||||||
|
System.out.println("活跃用户数: " + userStats.getActiveUsers());
|
||||||
|
System.out.println("访客用户数: " + userStats.getGuestUsers());
|
||||||
|
System.out.println("总对话数: " + contentStats.getTotalConversations());
|
||||||
|
System.out.println("总消息数: " + contentStats.getTotalMessages());
|
||||||
|
System.out.println("日记帖子数: " + contentStats.getDiaryPosts());
|
||||||
|
System.out.println("社区帖子数: " + contentStats.getCommunityPosts());
|
||||||
|
System.out.println("情绪记录数: " + contentStats.getEmotionRecords());
|
||||||
|
System.out.println("总API调用次数: " + aiStats.getTotalApiCalls());
|
||||||
|
System.out.println("今日API调用次数: " + aiStats.getTodayApiCalls());
|
||||||
|
System.out.println("成功调用次数: " + aiStats.getSuccessfulCalls());
|
||||||
|
System.out.println("失败调用次数: " + aiStats.getFailedCalls());
|
||||||
|
System.out.println("平均响应时间: " + aiStats.getAvgResponseTime() + "ms");
|
||||||
|
System.out.println("AI配置数量: " + aiStats.getAiConfigCount());
|
||||||
|
System.out.println("管理员数量: " + systemStats.getAdminCount());
|
||||||
|
System.out.println("成就数量: " + systemStats.getAchievementCount());
|
||||||
|
System.out.println("奖励数量: " + systemStats.getRewardCount());
|
||||||
|
System.out.println("系统运行时间: " + systemStats.getUptime());
|
||||||
|
System.out.println("最近活动数量: " + stats.getRecentActivities().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserStats() {
|
||||||
|
// 测试获取用户统计数据
|
||||||
|
DashboardStatsResponse.UserStats userStats = dashboardService.getUserStats();
|
||||||
|
|
||||||
|
assertNotNull(userStats, "用户统计数据不应为空");
|
||||||
|
assertTrue(userStats.getTotalUsers() >= 0, "总用户数应大于等于0");
|
||||||
|
assertTrue(userStats.getTodayNewUsers() >= 0, "今日新增用户数应大于等于0");
|
||||||
|
assertTrue(userStats.getActiveUsers() >= 0, "活跃用户数应大于等于0");
|
||||||
|
assertTrue(userStats.getGuestUsers() >= 0, "访客用户数应大于等于0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetContentStats() {
|
||||||
|
// 测试获取内容统计数据
|
||||||
|
DashboardStatsResponse.ContentStats contentStats = dashboardService.getContentStats();
|
||||||
|
|
||||||
|
assertNotNull(contentStats, "内容统计数据不应为空");
|
||||||
|
assertTrue(contentStats.getTotalConversations() >= 0, "总对话数应大于等于0");
|
||||||
|
assertTrue(contentStats.getTotalMessages() >= 0, "总消息数应大于等于0");
|
||||||
|
assertTrue(contentStats.getDiaryPosts() >= 0, "日记帖子数应大于等于0");
|
||||||
|
assertTrue(contentStats.getCommunityPosts() >= 0, "社区帖子数应大于等于0");
|
||||||
|
assertTrue(contentStats.getEmotionRecords() >= 0, "情绪记录数应大于等于0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAiServiceStats() {
|
||||||
|
// 测试获取AI服务统计数据
|
||||||
|
DashboardStatsResponse.AiServiceStats aiStats = dashboardService.getAiServiceStats();
|
||||||
|
|
||||||
|
assertNotNull(aiStats, "AI服务统计数据不应为空");
|
||||||
|
assertTrue(aiStats.getTotalApiCalls() >= 0, "总API调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getTodayApiCalls() >= 0, "今日API调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getSuccessfulCalls() >= 0, "成功调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getFailedCalls() >= 0, "失败调用次数应大于等于0");
|
||||||
|
assertTrue(aiStats.getAvgResponseTime() >= 0, "平均响应时间应大于等于0");
|
||||||
|
assertTrue(aiStats.getAiConfigCount() >= 0, "AI配置数量应大于等于0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSystemStats() {
|
||||||
|
// 测试获取系统统计数据
|
||||||
|
DashboardStatsResponse.SystemStats systemStats = dashboardService.getSystemStats();
|
||||||
|
|
||||||
|
assertNotNull(systemStats, "系统统计数据不应为空");
|
||||||
|
assertTrue(systemStats.getAdminCount() >= 0, "管理员数量应大于等于0");
|
||||||
|
assertTrue(systemStats.getAchievementCount() >= 0, "成就数量应大于等于0");
|
||||||
|
assertTrue(systemStats.getRewardCount() >= 0, "奖励数量应大于等于0");
|
||||||
|
assertNotNull(systemStats.getUptime(), "系统运行时间不应为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user