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