feat:使用SSE实现轻量消息推送

This commit is contained in:
czhqwer
2025-03-30 02:26:21 +08:00
parent a312010f17
commit 71909ce798
11 changed files with 253 additions and 3 deletions
@@ -3,10 +3,12 @@ package cn.czh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@EnableAsync
@SpringBootApplication
public class Main {
@@ -1,11 +1,14 @@
package cn.czh.controller;
import cn.czh.base.Result;
import cn.czh.dto.NotifyMessage;
import cn.czh.entity.StorageConfig;
import cn.czh.service.IAuthService;
import cn.czh.service.IFileService;
import cn.czh.service.ISseService;
import cn.czh.service.IStorageConfigService;
import cn.czh.utils.FileTypeUtil;
import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
@@ -24,6 +27,7 @@ import java.net.NetworkInterface;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.Map;
@Slf4j
@RestController
@@ -36,6 +40,8 @@ public class FileController {
private IFileService fileService;
@Resource
private IAuthService authService;
@Resource
private ISseService sseService;
@Value("${server.port}")
private String serverPort;
@@ -147,6 +153,8 @@ public class FileController {
return Result.error("非主用户不能修改分享状态");
}
enableShare = enable;
Map<String, Object> data = MapUtil.of("enable", enable);
sseService.notifySystemEvent(new NotifyMessage("enableShare", data));
return Result.success();
}
@@ -0,0 +1,34 @@
package cn.czh.controller;
import cn.czh.service.ISseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/sse")
public class SseController {
@Resource
private ISseService sseService;
@GetMapping("/subscribeSharedFiles")
public SseEmitter subscribeSharedFiles(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
return sseService.subscribeSharedFiles(remoteAddr);
}
@GetMapping("/subscribeSystemEvent")
public SseEmitter subscribeSystemEvent(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
return sseService.subscribeSystemEvent(remoteAddr);
}
}
@@ -0,0 +1,16 @@
package cn.czh.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Map;
@AllArgsConstructor
@Data
public class NotifyMessage {
private String type;
private Map<String, Object> data;
}
@@ -0,0 +1,28 @@
package cn.czh.service;
import cn.czh.dto.NotifyMessage;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
public interface ISseService {
/**
* 订阅共享文件更新
*/
SseEmitter subscribeSharedFiles(String clientId);
/**
* 订阅系统事件
*/
SseEmitter subscribeSystemEvent(String clientId);
/**
* 通知共享文件更新
*/
void notifySharedFileUpdate(String message);
/**
* 通知系统事件
*/
void notifySystemEvent(NotifyMessage message);
}
@@ -1,20 +1,26 @@
package cn.czh.service.impl;
import cn.czh.dto.NotifyMessage;
import cn.czh.entity.UserConfig;
import cn.czh.mapper.UserConfigMapper;
import cn.czh.service.IAuthService;
import cn.czh.service.ISseService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
@Service
public class AuthServiceImpl implements IAuthService {
@Resource
private UserConfigMapper userConfigMapper;
@Resource
private ISseService sseService;
@Override
public String getMainUserPassword() {
@@ -48,6 +54,11 @@ public class AuthServiceImpl implements IAuthService {
} else {
userConfigMapper.updateById(user);
}
Map<String, Object> data = new HashMap<>();
data.put("password", password);
NotifyMessage message = new NotifyMessage("setPassword", data);
sseService.notifySystemEvent(message);
}
@Override
@@ -6,6 +6,10 @@ import cn.czh.entity.UploadFile;
import cn.czh.mapper.ShareFileMapper;
import cn.czh.mapper.UploadFileMapper;
import cn.czh.service.IFileService;
import cn.czh.service.ISseService;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -24,6 +28,8 @@ public class FileServiceImpl implements IFileService {
private UploadFileMapper uploadFileMapper;
@Resource
private ShareFileMapper shareFileMapper;
@Resource
private ISseService sseService;
@Override
public IPage<UploadFile> pageFiles(Integer page, Integer pageSize, String storageType, String fileName) {
@@ -70,12 +76,14 @@ public class FileServiceImpl implements IFileService {
sharedFile.setFileIdentifier(identifier);
sharedFile.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
shareFileMapper.insert(sharedFile);
sseService.notifySharedFileUpdate("add");
}
}
@Override
public void removeSharedFile(String fileIdentifier) {
shareFileMapper.delete(Wrappers.lambdaQuery(SharedFile.class).eq(SharedFile::getFileIdentifier, fileIdentifier));
sseService.notifySharedFileUpdate("remove");
}
@Override
@@ -0,0 +1,72 @@
package cn.czh.service.impl;
import cn.czh.dto.NotifyMessage;
import cn.czh.service.ISseService;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class SseServiceImpl implements ISseService {
private final Map<String, SseEmitter> sharedFilesEmitters = new ConcurrentHashMap<>();
private final Map<String, SseEmitter> systemEventEmitters = new ConcurrentHashMap<>();
@Override
public SseEmitter subscribeSharedFiles(String clientId) {
SseEmitter emitter = new SseEmitter(0L); // 设置超时时间
sharedFilesEmitters.put(clientId, emitter);
emitter.onCompletion(() -> sharedFilesEmitters.remove(clientId));
emitter.onTimeout(() -> sharedFilesEmitters.remove(clientId));
return emitter;
}
@Override
public SseEmitter subscribeSystemEvent(String clientId) {
SseEmitter emitter = new SseEmitter(0L); // 设置超时时间
systemEventEmitters.put(clientId, emitter);
emitter.onCompletion(() -> systemEventEmitters.remove(clientId));
emitter.onTimeout(() -> systemEventEmitters.remove(clientId));
return emitter;
}
@Async
@Override
public void notifySharedFileUpdate(String message) {
sharedFilesEmitters.forEach((clientId, emitter) ->
sendNotification(clientId, emitter, "sharedFileUpdate", message, sharedFilesEmitters));
}
@Async
@Override
public void notifySystemEvent(NotifyMessage message) {
systemEventEmitters.forEach((clientId, emitter) ->
sendNotification(clientId, emitter, "systemEvent", JSONUtil.toJsonStr(message), systemEventEmitters));
}
private void sendNotification(String clientId, SseEmitter emitter, String event, String message, Map<String, SseEmitter> emittersMap) {
try {
if (emitter != null) {
emitter.send(SseEmitter.event().name(event).data(message));
} else {
log.warn("Emitter for client {} is disposed or null, skipping notification", clientId);
}
} catch (IOException e) {
log.error("Error sending notification to client: {}", clientId, e);
emittersMap.remove(clientId); // 从对应的Map中移除
} catch (Exception e) {
log.error("Unexpected error sending notification to client: {}", clientId, e);
emittersMap.remove(clientId);
}
}
}