refactor(backend): 重命名后端项目为 gym-manage-api,修改包名为 cn.novalon.gym.manage

This commit is contained in:
张翔
2026-04-17 18:35:50 +08:00
parent 666189b676
commit deb961c427
916 changed files with 108360 additions and 38328 deletions
@@ -0,0 +1,33 @@
package cn.novalon.gym.manage.notify.config;
import cn.novalon.gym.manage.notify.websocket.SysWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class WebSocketConfig {
@Bean
public HandlerMapping webSocketHandlerMapping(SysWebSocketHandler webSocketHandler) {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/ws", webSocketHandler);
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
handlerMapping.setUrlMap(map);
return handlerMapping;
}
@Bean
public WebSocketHandlerAdapter webSocketHandlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
@@ -0,0 +1,97 @@
package cn.novalon.gym.manage.notify.core.domain;
import java.time.LocalDateTime;
public class SysNotice {
private Long id;
private String noticeTitle;
private String noticeType;
private String noticeContent;
private String status;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNoticeTitle() {
return noticeTitle;
}
public void setNoticeTitle(String noticeTitle) {
this.noticeTitle = noticeTitle;
}
public String getNoticeType() {
return noticeType;
}
public void setNoticeType(String noticeType) {
this.noticeType = noticeType;
}
public String getNoticeContent() {
return noticeContent;
}
public void setNoticeContent(String noticeContent) {
this.noticeContent = noticeContent;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
@@ -0,0 +1,29 @@
package cn.novalon.gym.manage.notify.core.domain;
import java.time.LocalDateTime;
public class SysUserMessage {
private Long id;
private Long userId;
private String title;
private String content;
private String messageType;
private String isRead;
private LocalDateTime createTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getMessageType() { return messageType; }
public void setMessageType(String messageType) { this.messageType = messageType; }
public String getIsRead() { return isRead; }
public void setIsRead(String isRead) { this.isRead = isRead; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}
@@ -0,0 +1,38 @@
package cn.novalon.gym.manage.notify.core.query;
/**
* 用户消息查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysUserMessageQuery {
private Long userId;
private String isRead;
private String keyword;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getIsRead() {
return isRead;
}
public void setIsRead(String isRead) {
this.isRead = isRead;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,18 @@
package cn.novalon.gym.manage.notify.core.repository;
import cn.novalon.gym.manage.notify.core.domain.SysNotice;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysNoticeRepository {
Flux<SysNotice> findByDeletedAtIsNull();
Flux<SysNotice> findByStatusAndDeletedAtIsNull(String status);
Mono<SysNotice> findById(Long id);
Mono<SysNotice> save(SysNotice notice);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,20 @@
package cn.novalon.gym.manage.notify.core.repository;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysUserMessageRepository {
Flux<SysUserMessage> findByUserIdOrderByCreateTimeDesc(Long userId);
Flux<SysUserMessage> findByUserIdAndIsReadOrderByCreateTimeDesc(Long userId, String isRead);
Mono<Long> countByUserIdAndIsRead(Long userId, String isRead);
Mono<SysUserMessage> save(SysUserMessage message);
Mono<SysUserMessage> findById(Long id);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,20 @@
package cn.novalon.gym.manage.notify.core.service;
import cn.novalon.gym.manage.notify.core.domain.SysNotice;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysNoticeService {
Flux<SysNotice> getAllNotices();
Mono<SysNotice> getNoticeById(Long id);
Flux<SysNotice> getNoticesByStatus(String status);
Mono<SysNotice> createNotice(SysNotice notice);
Mono<SysNotice> updateNotice(Long id, SysNotice notice);
Mono<Void> deleteNotice(Long id);
}
@@ -0,0 +1,20 @@
package cn.novalon.gym.manage.notify.core.service;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysUserMessageService {
Flux<SysUserMessage> getMessagesByUser(Long userId);
Mono<Long> getUnreadCount(Long userId);
Flux<SysUserMessage> getUnreadMessages(Long userId);
Mono<SysUserMessage> createMessage(SysUserMessage message);
Mono<SysUserMessage> markAsRead(Long id);
Mono<Void> deleteMessage(Long id);
}
@@ -0,0 +1,74 @@
package cn.novalon.gym.manage.notify.core.service.impl;
import cn.novalon.gym.manage.notify.core.domain.SysNotice;
import cn.novalon.gym.manage.notify.core.repository.ISysNoticeRepository;
import cn.novalon.gym.manage.notify.core.service.ISysNoticeService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
@Service
public class SysNoticeServiceImpl implements ISysNoticeService {
private final ISysNoticeRepository noticeRepository;
public SysNoticeServiceImpl(ISysNoticeRepository noticeRepository) {
this.noticeRepository = noticeRepository;
}
@Override
public Flux<SysNotice> getAllNotices() {
return noticeRepository.findByDeletedAtIsNull();
}
@Override
public Mono<SysNotice> getNoticeById(Long id) {
return noticeRepository.findById(id)
.filter(notice -> notice.getDeletedAt() == null);
}
@Override
public Flux<SysNotice> getNoticesByStatus(String status) {
return noticeRepository.findByStatusAndDeletedAtIsNull(status);
}
@Override
public Mono<SysNotice> createNotice(SysNotice notice) {
notice.setCreatedAt(LocalDateTime.now());
return noticeRepository.save(notice);
}
@Override
public Mono<SysNotice> updateNotice(Long id, SysNotice notice) {
return noticeRepository.findById(id)
.flatMap(existingNotice -> {
if (notice.getNoticeTitle() != null) {
existingNotice.setNoticeTitle(notice.getNoticeTitle());
}
if (notice.getNoticeContent() != null) {
existingNotice.setNoticeContent(notice.getNoticeContent());
}
if (notice.getStatus() != null) {
existingNotice.setStatus(notice.getStatus());
}
if (notice.getNoticeType() != null) {
existingNotice.setNoticeType(notice.getNoticeType());
}
existingNotice.setUpdatedAt(LocalDateTime.now());
return noticeRepository.save(existingNotice);
});
}
@Override
public Mono<Void> deleteNotice(Long id) {
return noticeRepository.findById(id)
.filter(notice -> notice.getDeletedAt() == null)
.flatMap(notice -> {
notice.setDeletedAt(LocalDateTime.now());
return noticeRepository.save(notice);
})
.then();
}
}
@@ -0,0 +1,56 @@
package cn.novalon.gym.manage.notify.core.service.impl;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import cn.novalon.gym.manage.notify.core.repository.ISysUserMessageRepository;
import cn.novalon.gym.manage.notify.core.service.ISysUserMessageService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
@Service
public class SysUserMessageServiceImpl implements ISysUserMessageService {
private final ISysUserMessageRepository messageRepository;
public SysUserMessageServiceImpl(ISysUserMessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
@Override
public Flux<SysUserMessage> getMessagesByUser(Long userId) {
return messageRepository.findByUserIdOrderByCreateTimeDesc(userId);
}
@Override
public Mono<Long> getUnreadCount(Long userId) {
return messageRepository.countByUserIdAndIsRead(userId, "0");
}
@Override
public Flux<SysUserMessage> getUnreadMessages(Long userId) {
return messageRepository.findByUserIdAndIsReadOrderByCreateTimeDesc(userId, "0");
}
@Override
public Mono<SysUserMessage> createMessage(SysUserMessage message) {
message.setCreateTime(LocalDateTime.now());
message.setIsRead("0");
return messageRepository.save(message);
}
@Override
public Mono<SysUserMessage> markAsRead(Long id) {
return messageRepository.findById(id)
.flatMap(message -> {
message.setIsRead("1");
return messageRepository.save(message);
});
}
@Override
public Mono<Void> deleteMessage(Long id) {
return messageRepository.deleteById(id);
}
}
@@ -0,0 +1,92 @@
package cn.novalon.gym.manage.notify.handler;
import cn.novalon.gym.manage.notify.core.domain.SysNotice;
import cn.novalon.gym.manage.notify.core.service.ISysNoticeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Component
@Tag(name = "通知管理", description = "系统通知相关操作")
public class SysNoticeHandler {
private final ISysNoticeService noticeService;
private static final List<String> VALID_NOTICE_TYPES = Arrays.asList("1", "2");
private static final List<String> VALID_STATUSES = Arrays.asList("0", "1");
public SysNoticeHandler(ISysNoticeService noticeService) {
this.noticeService = noticeService;
}
@Operation(summary = "获取所有通知", description = "获取系统中所有通知列表")
public Mono<ServerResponse> getAllNotices(ServerRequest request) {
Flux<SysNotice> notices = noticeService.getAllNotices();
return ServerResponse.ok().body(notices, SysNotice.class);
}
@Operation(summary = "根据ID获取通知", description = "根据通知ID获取通知详细信息")
public Mono<ServerResponse> getNoticeById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return noticeService.getNoticeById(id)
.flatMap(notice -> ServerResponse.ok().bodyValue(notice))
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "根据状态获取通知", description = "根据状态获取通知列表")
public Mono<ServerResponse> getNoticesByStatus(ServerRequest request) {
String status = request.pathVariable("status");
Flux<SysNotice> notices = noticeService.getNoticesByStatus(status);
return ServerResponse.ok().body(notices, SysNotice.class);
}
@Operation(summary = "创建通知", description = "创建新通知")
public Mono<ServerResponse> createNotice(ServerRequest request) {
return request.bodyToMono(SysNotice.class)
.filter(notice -> notice.getNoticeTitle() != null && !notice.getNoticeTitle().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("公告标题不能为空")))
.filter(notice -> VALID_NOTICE_TYPES.contains(notice.getNoticeType()))
.switchIfEmpty(Mono.error(new IllegalArgumentException("公告类型必须是1(通知)或2(公告)")))
.filter(notice -> notice.getNoticeContent() != null && !notice.getNoticeContent().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("公告内容不能为空")))
.filter(notice -> notice.getStatus() == null || VALID_STATUSES.contains(notice.getStatus()))
.switchIfEmpty(Mono.error(new IllegalArgumentException("状态必须是0(正常)或1(关闭)")))
.flatMap(noticeService::createNotice)
.flatMap(notice -> ServerResponse.created(request.uriBuilder().path("/{id}").build(notice.getId())).bodyValue(notice))
.onErrorResume(IllegalArgumentException.class, ex -> {
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", ex.getMessage(),
"timestamp", LocalDateTime.now()
));
});
}
@Operation(summary = "更新通知", description = "更新通知信息")
public Mono<ServerResponse> updateNotice(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return request.bodyToMono(SysNotice.class)
.flatMap(notice -> noticeService.updateNotice(id, notice))
.flatMap(notice -> ServerResponse.ok().bodyValue(notice))
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "删除通知", description = "删除指定通知")
public Mono<ServerResponse> deleteNotice(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return noticeService.getNoticeById(id)
.filter(notice -> notice.getDeletedAt() == null)
.flatMap(notice -> noticeService.deleteNotice(id)
.then(ServerResponse.noContent().build()))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
@@ -0,0 +1,57 @@
package cn.novalon.gym.manage.notify.handler;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import cn.novalon.gym.manage.notify.core.service.ISysUserMessageService;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
public class SysUserMessageHandler {
private final ISysUserMessageService messageService;
public SysUserMessageHandler(ISysUserMessageService messageService) {
this.messageService = messageService;
}
public Mono<ServerResponse> getMessagesByUser(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
Flux<SysUserMessage> messages = messageService.getMessagesByUser(userId);
return ServerResponse.ok().body(messages, SysUserMessage.class);
}
public Mono<ServerResponse> getUnreadCount(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
return messageService.getUnreadCount(userId)
.flatMap(count -> ServerResponse.ok().bodyValue(count));
}
public Mono<ServerResponse> getUnreadList(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
Flux<SysUserMessage> messages = messageService.getUnreadMessages(userId);
return ServerResponse.ok().body(messages, SysUserMessage.class);
}
public Mono<ServerResponse> createMessage(ServerRequest request) {
return request.bodyToMono(SysUserMessage.class)
.flatMap(messageService::createMessage)
.flatMap(message -> ServerResponse.ok().bodyValue(message));
}
public Mono<ServerResponse> markAsRead(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return messageService.markAsRead(id)
.flatMap(message -> ServerResponse.ok().bodyValue(message))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> deleteMessage(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return messageService.deleteMessage(id)
.then(ServerResponse.ok().build())
.switchIfEmpty(ServerResponse.notFound().build());
}
}
@@ -0,0 +1,161 @@
package cn.novalon.gym.manage.notify.websocket;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class SysWebSocketHandler implements WebSocketHandler {
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
private final Map<String, LocalDateTime> lastActivityTime = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
@Value("${websocket.idle-timeout:300s}")
private Duration idleTimeout;
@Value("${websocket.heartbeat-interval:30s}")
private Duration heartbeatInterval;
@Override
public Mono<Void> handle(WebSocketSession session) {
String userId = extractUserId(session);
lastActivityTime.put(userId, LocalDateTime.now());
return session.receive()
.doOnNext(message -> {
String payload = message.getPayloadAsText();
handleIncomingMessage(session, userId, payload);
lastActivityTime.put(userId, LocalDateTime.now());
})
.doOnComplete(() -> {
sessions.remove(userId);
lastActivityTime.remove(userId);
System.out.println("WebSocket session closed for user: " + userId);
})
.doOnError(error -> {
sessions.remove(userId);
lastActivityTime.remove(userId);
System.err.println("WebSocket error for user " + userId + ": " + error.getMessage());
})
.then();
}
@Scheduled(fixedRate = 60000)
public void cleanupIdleConnections() {
LocalDateTime now = LocalDateTime.now();
lastActivityTime.entrySet().removeIf(entry -> {
LocalDateTime lastActivity = entry.getValue();
if (Duration.between(lastActivity, now).compareTo(idleTimeout) > 0) {
String userId = entry.getKey();
WebSocketSession session = sessions.get(userId);
if (session != null) {
try {
session.close();
System.out.println("Closed idle WebSocket connection for user: " + userId);
} catch (Exception e) {
System.err.println("Error closing idle connection for user " + userId + ": " + e.getMessage());
}
}
return true;
}
return false;
});
}
@Scheduled(fixedRate = 30000)
public void sendHeartbeat() {
sessions.forEach((userId, session) -> {
try {
if (session.isOpen()) {
String heartbeatMessage = objectMapper.writeValueAsString(Map.of(
"type", "heartbeat",
"timestamp", System.currentTimeMillis()
));
session.send(Mono.just(session.textMessage(heartbeatMessage))).subscribe();
}
} catch (Exception e) {
System.err.println("Error sending heartbeat to user " + userId + ": " + e.getMessage());
}
});
}
private String extractUserId(WebSocketSession session) {
String query = session.getHandshakeInfo().getUri().getQuery();
if (query != null && query.contains("userId=")) {
return query.split("userId=")[1].split("&")[0];
}
return session.getId();
}
private void handleIncomingMessage(WebSocketSession session, String userId, String payload) {
try {
Map<String, Object> message = objectMapper.readValue(payload, new TypeReference<Map<String, Object>>() {
});
String type = (String) message.get("type");
switch (type) {
case "ping":
sendMessageToUser(userId, Map.of("type", "pong", "timestamp", System.currentTimeMillis()));
break;
case "pong":
lastActivityTime.put(userId, LocalDateTime.now());
break;
case "subscribe":
sessions.put(userId, session);
lastActivityTime.put(userId, LocalDateTime.now());
System.out.println("User " + userId + " subscribed to WebSocket");
break;
case "heartbeat":
lastActivityTime.put(userId, LocalDateTime.now());
break;
default:
System.out.println("Unknown message type: " + type);
}
} catch (Exception e) {
System.err.println("Error handling WebSocket message: " + e.getMessage());
}
}
public void sendMessageToUser(String userId, Object message) {
WebSocketSession session = sessions.get(userId);
if (session != null && session.isOpen()) {
try {
String json = objectMapper.writeValueAsString(message);
session.send(Mono.just(session.textMessage(json))).subscribe();
} catch (Exception e) {
System.err.println("Error sending message to user " + userId + ": " + e.getMessage());
}
}
}
public void broadcastMessage(Object message) {
String json;
try {
json = objectMapper.writeValueAsString(message);
} catch (Exception e) {
System.err.println("Error serializing broadcast message: " + e.getMessage());
return;
}
sessions.forEach((userId, session) -> {
try {
if (session.isOpen()) {
session.send(Mono.just(session.textMessage(json))).subscribe();
}
} catch (Exception e) {
System.err.println("Error broadcasting to user " + userId + ": " + e.getMessage());
}
});
}
}
@@ -0,0 +1 @@
cn.novalon.manage.notify.config.WebSocketConfig
@@ -0,0 +1,253 @@
package cn.novalon.gym.manage.notify.handler;
import cn.novalon.gym.manage.notify.core.domain.SysNotice;
import cn.novalon.gym.manage.notify.core.service.ISysNoticeService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysNoticeHandlerTest {
@Mock
private ISysNoticeService noticeService;
private SysNoticeHandler noticeHandler;
private SysNotice testNotice;
@BeforeEach
void setUp() {
noticeHandler = new SysNoticeHandler(noticeService);
testNotice = new SysNotice();
testNotice.setId(1L);
testNotice.setNoticeTitle("系统维护通知");
testNotice.setNoticeType("SYSTEM");
testNotice.setNoticeContent("系统将于今晚进行维护");
testNotice.setStatus("PUBLISHED");
testNotice.setCreateBy("admin");
testNotice.setCreatedAt(LocalDateTime.now());
}
@Test
void testGetAllNotices() {
when(noticeService.getAllNotices()).thenReturn(Flux.just(testNotice));
ServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> response = noticeHandler.getAllNotices(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getAllNotices();
}
@Test
void testGetNoticeById() {
when(noticeService.getNoticeById(1L)).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticeById(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getNoticeById(1L);
}
@Test
void testGetNoticeById_NotFound() {
when(noticeService.getNoticeById(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticeById(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(noticeService).getNoticeById(999L);
}
@Test
void testGetNoticesByStatus() {
when(noticeService.getNoticesByStatus("PUBLISHED")).thenReturn(Flux.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.pathVariable("status", "PUBLISHED")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticesByStatus(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getNoticesByStatus("PUBLISHED");
}
@Test
void testGetNoticesByStatus_Draft() {
when(noticeService.getNoticesByStatus("DRAFT")).thenReturn(Flux.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("status", "DRAFT")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticesByStatus(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getNoticesByStatus("DRAFT");
}
@Test
void testCreateNotice() {
SysNotice newNotice = new SysNotice();
newNotice.setNoticeTitle("新通知");
newNotice.setNoticeType("1");
newNotice.setNoticeContent("测试内容");
newNotice.setStatus("0");
when(noticeService.createNotice(any(SysNotice.class))).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(newNotice));
Mono<ServerResponse> response = noticeHandler.createNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.CREATED)
.verifyComplete();
verify(noticeService).createNotice(any(SysNotice.class));
}
@Test
void testCreateNotice_WithAllFields() {
SysNotice newNotice = new SysNotice();
newNotice.setNoticeTitle("完整通知");
newNotice.setNoticeType("2");
newNotice.setNoticeContent("完整内容");
newNotice.setStatus("1");
newNotice.setCreateBy("admin");
when(noticeService.createNotice(any(SysNotice.class))).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(newNotice));
Mono<ServerResponse> response = noticeHandler.createNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.CREATED)
.verifyComplete();
verify(noticeService).createNotice(any(SysNotice.class));
}
@Test
void testUpdateNotice() {
SysNotice updateNotice = new SysNotice();
updateNotice.setNoticeTitle("更新后的通知");
updateNotice.setNoticeType("SYSTEM");
updateNotice.setNoticeContent("更新后的内容");
updateNotice.setStatus("PUBLISHED");
when(noticeService.updateNotice(anyLong(), any(SysNotice.class))).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.body(Mono.just(updateNotice));
Mono<ServerResponse> response = noticeHandler.updateNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).updateNotice(1L, updateNotice);
}
@Test
void testUpdateNotice_NotFound() {
SysNotice updateNotice = new SysNotice();
updateNotice.setNoticeTitle("更新后的通知");
when(noticeService.updateNotice(anyLong(), any(SysNotice.class))).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.body(Mono.just(updateNotice));
Mono<ServerResponse> response = noticeHandler.updateNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(noticeService).updateNotice(999L, updateNotice);
}
@Test
void testDeleteNotice() {
when(noticeService.getNoticeById(1L)).thenReturn(Mono.just(testNotice));
when(noticeService.deleteNotice(1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = noticeHandler.deleteNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NO_CONTENT)
.verifyComplete();
verify(noticeService).getNoticeById(1L);
verify(noticeService).deleteNotice(1L);
}
@Test
void testDeleteNotice_NotFound() {
when(noticeService.getNoticeById(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = noticeHandler.deleteNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(noticeService).getNoticeById(999L);
}
}
@@ -0,0 +1,181 @@
package cn.novalon.gym.manage.notify.websocket;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.net.URI;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysWebSocketHandlerTest {
@Mock
private WebSocketSession session;
@Mock
private WebSocketMessage message;
@Mock
private HandshakeInfo handshakeInfo;
private SysWebSocketHandler webSocketHandler;
@BeforeEach
void setUp() {
webSocketHandler = new SysWebSocketHandler();
}
@Test
void testHandle_NewConnection() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(session.receive()).thenReturn(Flux.empty());
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
}
@Test
void testHandle_WithUserId() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=123"));
when(session.receive()).thenReturn(Flux.empty());
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
}
@Test
void testHandle_WithoutUserId() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws"));
when(session.getId()).thenReturn("test-session-id");
when(session.receive()).thenReturn(Flux.empty());
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
}
@Test
void testHandle_PongMessage() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"pong\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_SubscribeMessage() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"subscribe\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_HeartbeatMessage() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"heartbeat\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_UnknownMessageType() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"unknown\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_InvalidJson() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("invalid json");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_SessionError() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(session.receive()).thenReturn(Flux.error(new RuntimeException("Connection error")));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyError();
verify(session).receive();
}
@Test
void testSendMessageToUser_SessionNotFound() {
webSocketHandler.sendMessageToUser("nonexistent", java.util.Map.of("type", "notification", "message", "test"));
verify(session, never()).send(any());
}
}