diff --git a/pom.xml b/pom.xml
index 1bc34b4..b69ee4e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,17 @@
shardingsphere-jdbc-core-spring-boot-starter
5.2.0
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+
+ com.lmax
+ disruptor
+ 3.4.2
+
org.springframework.boot
spring-boot-configuration-processor
diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/MainController.java b/src/main/java/edu/whut/smilepicturebackend/controller/MainController.java
index 818221e..6630e29 100644
--- a/src/main/java/edu/whut/smilepicturebackend/controller/MainController.java
+++ b/src/main/java/edu/whut/smilepicturebackend/controller/MainController.java
@@ -13,8 +13,14 @@ public class MainController {
* 健康检查
*/
@GetMapping("/health")
- public BaseResponse health() {
+ public BaseResponse health() throws InterruptedException {
+// new Thread(() -> {
+// try {
+// Thread.sleep(100000L);
+// } catch (InterruptedException e) {
+// throw new RuntimeException(e);
+// }
+// }).start();
return ResultUtils.success("ok");
}
-
}
diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java
index 7bd4d9f..23f7dd1 100644
--- a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java
+++ b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java
@@ -175,7 +175,7 @@ public class PictureController {
// 查询数据库
Picture picture = pictureService.getById(id);
ThrowUtils.throwIf(picture == null, ErrorCode.NOT_FOUND_ERROR);
- ThrowUtils.throwIf(PictureReviewStatusEnum.PASS.getValue()!=picture.getReviewStatus(),ErrorCode.NOT_FOUND_ERROR);
+// ThrowUtils.throwIf(PictureReviewStatusEnum.PASS.getValue()!=picture.getReviewStatus(),ErrorCode.NOT_FOUND_ERROR);
// 空间权限校验
Long spaceId = picture.getSpaceId();
Space space = null;
@@ -186,6 +186,8 @@ public class PictureController {
//User loginUser = userService.getLoginUser(request);
//已经改为使用sa-token鉴权
//pictureService.checkPictureAuth(loginUser, picture);
+ space = spaceService.getById(spaceId);
+ ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
}
// 获取权限列表
User loginUser = userService.getLoginUser(request);
@@ -228,7 +230,7 @@ public class PictureController {
pictureQueryRequest.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
pictureQueryRequest.setNullSpaceId(true);
} else {
- // 私有空间
+ // 私有空间、团队空间,无需过审
boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW);
ThrowUtils.throwIf(!hasPermission, ErrorCode.NO_AUTH_ERROR);
//已改为编程式鉴权
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/PictureEditHandler.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/PictureEditHandler.java
new file mode 100644
index 0000000..2be2375
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/PictureEditHandler.java
@@ -0,0 +1,262 @@
+package edu.whut.smilepicturebackend.manager.websocket;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import edu.whut.smilepicturebackend.manager.websocket.disruptor.PictureEditEventProducer;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditActionEnum;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditMessageTypeEnum;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditRequestMessage;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditResponseMessage;
+import edu.whut.smilepicturebackend.model.entity.User;
+import edu.whut.smilepicturebackend.service.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 图片编辑 WebSocket 处理器
+ */
+@Component
+@Slf4j
+public class PictureEditHandler extends TextWebSocketHandler {
+
+ @Resource
+ private UserService userService;
+
+ @Resource
+ @Lazy
+ private PictureEditEventProducer pictureEditEventProducer;
+
+ // 每张图片的编辑状态,key: pictureId, value: 当前正在编辑的用户 ID
+ private final Map pictureEditingUsers = new ConcurrentHashMap<>();
+
+ // 保存所有连接的会话,key: pictureId, value: 所有正在编辑这张图片的用户会话集合
+ private final Map> pictureSessions = new ConcurrentHashMap<>();
+
+ /**
+ * 实现连接建立成功后执行的方法,保存会话到集合中,并且给其他会话发送消息
+ *
+ * @param session
+ * @throws Exception
+ */
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+ super.afterConnectionEstablished(session);
+ // 保存会话到集合中
+ User user = (User) session.getAttributes().get("user");
+ Long pictureId = (Long) session.getAttributes().get("pictureId");
+ pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
+ pictureSessions.get(pictureId).add(session);
+ // 构造响应,发送加入编辑的消息通知
+ PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
+ pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
+ String message = String.format("用户 %s 加入编辑", user.getUserName());
+ pictureEditResponseMessage.setMessage(message);
+ pictureEditResponseMessage.setUser(userService.getUserVO(user));
+ // 广播给所有用户
+ broadcastToPicture(pictureId, pictureEditResponseMessage); //自己也可以收到自己发的
+ }
+
+ /**
+ * 编写接收客户端消息的方法,根据消息类别执行不同的处理
+ *
+ * @param session
+ * @param message
+ * @throws Exception
+ */
+ @Override
+ protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ super.handleTextMessage(session, message);
+ // 获取消息内容,将 JSON 转换为 PictureEditRequestMessage
+ PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
+ // 从 Session 属性中获取到公共参数
+ User user = (User) session.getAttributes().get("user");
+ Long pictureId = (Long) session.getAttributes().get("pictureId");
+ // 根据消息类型处理消息(生产消息到 Disruptor 环形队列中)
+ pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);
+ }
+
+ /**
+ * 进入编辑状态
+ *
+ * @param pictureEditRequestMessage
+ * @param session
+ * @param user
+ * @param pictureId
+ */
+ public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
+ // 没有用户正在编辑该图片,才能进入编辑
+ if (!pictureEditingUsers.containsKey(pictureId)) {
+ // 设置用户正在编辑该图片
+ pictureEditingUsers.put(pictureId, user.getId());
+ // 构造响应,发送加入编辑的消息通知
+ PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
+ pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
+ String message = String.format("用户 %s 开始编辑图片", user.getUserName());
+ pictureEditResponseMessage.setMessage(message);
+ pictureEditResponseMessage.setUser(userService.getUserVO(user));
+ // 广播给所有用户
+ broadcastToPicture(pictureId, pictureEditResponseMessage);
+ }
+ }
+
+ /**
+ * 处理编辑操作
+ *
+ * @param pictureEditRequestMessage
+ * @param session
+ * @param user
+ * @param pictureId
+ */
+ public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
+ // 正在编辑的用户
+ Long editingUserId = pictureEditingUsers.get(pictureId);
+ String editAction = pictureEditRequestMessage.getEditAction();
+ PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction);
+ if (actionEnum == null) {
+ log.error("无效的编辑动作");
+ return;
+ }
+ // 确认是当前的编辑者
+ if (editingUserId != null && editingUserId.equals(user.getId())) {
+ // 构造响应,发送具体操作的通知
+ PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
+ pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
+ String message = String.format("%s 执行 %s", user.getUserName(), actionEnum.getText());
+ pictureEditResponseMessage.setMessage(message);
+ pictureEditResponseMessage.setEditAction(editAction);
+ pictureEditResponseMessage.setUser(userService.getUserVO(user));
+ // 广播给除了当前客户端之外的其他用户,否则会造成重复编辑
+ broadcastToPicture(pictureId, pictureEditResponseMessage, session);
+ }
+ }
+
+
+ /**
+ * 用户退出编辑操作时,移除当前用户的编辑状态,并且向其他客户端发送消息
+ *
+ * @param pictureEditRequestMessage
+ * @param session
+ * @param user
+ * @param pictureId
+ */
+ public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
+ // 正在编辑的用户
+ Long editingUserId = pictureEditingUsers.get(pictureId);
+ // 确认是当前的编辑者
+ if (editingUserId != null && editingUserId.equals(user.getId())) {
+ // 移除用户正在编辑该图片
+ pictureEditingUsers.remove(pictureId);
+ // 构造响应,发送退出编辑的消息通知
+ PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
+ pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
+ String message = String.format("用户 %s 退出编辑图片", user.getUserName());
+ pictureEditResponseMessage.setMessage(message);
+ pictureEditResponseMessage.setUser(userService.getUserVO(user));
+ broadcastToPicture(pictureId, pictureEditResponseMessage);
+ }
+ }
+
+ /**
+ * WebSocket 连接关闭时,需要移除当前用户的编辑状态、并且从集合中删除当前会话,还可以给其他客户端发送消息通知
+ *
+ * @param session
+ * @param status
+ * @throws Exception
+ */
+ @Override
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+ super.afterConnectionClosed(session, status);
+ // 从 Session 属性中获取到公共参数
+ User user = (User) session.getAttributes().get("user");
+ Long pictureId = (Long) session.getAttributes().get("pictureId");
+ // 移除当前用户的编辑状态
+ handleExitEditMessage(null, session, user, pictureId);
+ // 删除会话
+ Set sessionSet = pictureSessions.get(pictureId);
+ if (sessionSet != null) {
+ sessionSet.remove(session);
+ if (sessionSet.isEmpty()) {
+ pictureSessions.remove(pictureId);
+ }
+ }
+ // 通知其他用户,该用户已经离开编辑
+ PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
+ pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
+ String message = String.format("用户 %s 离开编辑", user.getUserName());
+ pictureEditResponseMessage.setMessage(message);
+ pictureEditResponseMessage.setUser(userService.getUserVO(user));
+ broadcastToPicture(pictureId, pictureEditResponseMessage);
+ }
+
+ /**
+ * 广播给该图片的所有用户(支持排除掉某个 Session)
+ *
+ * @param pictureId
+ * @param pictureEditResponseMessage
+ * @param excludeSession
+ */
+ private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws IOException {
+ Set sessionSet = pictureSessions.get(pictureId);
+ if (CollUtil.isNotEmpty(sessionSet)) {
+ // 创建 ObjectMapper
+ ObjectMapper objectMapper = new ObjectMapper();
+ // 配置序列化:将 Long 类型转为 String,解决丢失精度问题
+ SimpleModule module = new SimpleModule();
+ module.addSerializer(Long.class, ToStringSerializer.instance);
+ module.addSerializer(Long.TYPE, ToStringSerializer.instance); // 支持 long 基本类型
+ objectMapper.registerModule(module);
+ // 序列化为 JSON 字符串
+ String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
+ TextMessage textMessage = new TextMessage(message);
+ for (WebSocketSession session : sessionSet) {
+ // 排除掉的 session 不发送 (比如自己发送的广播,自己不接收)
+ if (excludeSession != null && session.equals(excludeSession)) {
+ continue;
+ }
+ if (session.isOpen()) {
+ session.sendMessage(textMessage);
+ }
+ }
+ }
+ }
+
+ /**
+ * 广播给该图片的所有用户
+ *
+ * @param pictureId
+ * @param pictureEditResponseMessage
+ */
+ private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws IOException {
+ broadcastToPicture(pictureId, pictureEditResponseMessage, null);
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/WebSocketConfig.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/WebSocketConfig.java
new file mode 100644
index 0000000..5fb5e7e
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/WebSocketConfig.java
@@ -0,0 +1,28 @@
+package edu.whut.smilepicturebackend.manager.websocket;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+
+/**
+ * WebSocket 配置(定义连接)
+ */
+@Configuration
+@EnableWebSocket
+@RequiredArgsConstructor
+public class WebSocketConfig implements WebSocketConfigurer {
+
+ private final PictureEditHandler pictureEditHandler;
+
+ private final WsHandshakeInterceptor wsHandshakeInterceptor;
+
+ @Override
+ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+ ////当客户端在浏览器中执行new WebSocket("ws://<你的域名或 IP>:<端口>/ws/picture/edit?pictureId=123");就会由 Spring 把这个请求路由到你的 PictureEditHandler 实例。
+ registry.addHandler(pictureEditHandler, "/ws/picture/edit")
+ .addInterceptors(wsHandshakeInterceptor)
+ .setAllowedOrigins("*");
+ }
+}
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/WsHandshakeInterceptor.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/WsHandshakeInterceptor.java
new file mode 100644
index 0000000..54a55c7
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/WsHandshakeInterceptor.java
@@ -0,0 +1,103 @@
+package edu.whut.smilepicturebackend.manager.websocket;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import edu.whut.smilepicturebackend.manager.auth.SpaceUserAuthManager;
+import edu.whut.smilepicturebackend.manager.auth.model.SpaceUserPermissionConstant;
+import edu.whut.smilepicturebackend.model.entity.Picture;
+import edu.whut.smilepicturebackend.model.entity.Space;
+import edu.whut.smilepicturebackend.model.entity.User;
+import edu.whut.smilepicturebackend.model.enums.SpaceTypeEnum;
+import edu.whut.smilepicturebackend.service.PictureService;
+import edu.whut.smilepicturebackend.service.SpaceService;
+import edu.whut.smilepicturebackend.service.UserService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * WebSocket 拦截器,建立连接前要先校验 /如果只是公开的广播通道,不必写拦截器。
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WsHandshakeInterceptor implements HandshakeInterceptor {
+
+ private final UserService userService;
+
+ private final PictureService pictureService;
+
+ private final SpaceService spaceService;
+
+ private final SpaceUserAuthManager spaceUserAuthManager;
+
+ /**
+ * 建立连接前要先校验
+ *
+ * @param request
+ * @param response
+ * @param wsHandler
+ * @param attributes 给 WebSocketSession 会话设置属性
+ * @return
+ * @throws Exception
+ */
+ @Override
+ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
+ if (request instanceof ServletServerHttpRequest) {
+ HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest();
+ // 从请求中获取参数
+ String pictureId = httpServletRequest.getParameter("pictureId");
+ if (StrUtil.isBlank(pictureId)) {
+ log.error("缺少图片参数,拒绝握手");
+ return false;
+ }
+ // 获取当前登录用户
+ User loginUser = userService.getLoginUser(httpServletRequest);
+ if (ObjUtil.isEmpty(loginUser)) {
+ log.error("用户未登录,拒绝握手");
+ return false;
+ }
+ // 校验用户是否有编辑当前图片的权限
+ Picture picture = pictureService.getById(pictureId);
+ if (ObjUtil.isEmpty(picture)) {
+ log.error("图片不存在,拒绝握手");
+ return false;
+ }
+ Long spaceId = picture.getSpaceId();
+ Space space = null;
+ if (spaceId != null) {
+ space = spaceService.getById(spaceId);
+ if (ObjUtil.isEmpty(space)) {
+ log.error("图片所在空间不存在,拒绝握手");
+ return false;
+ }
+ if (space.getSpaceType() != SpaceTypeEnum.TEAM.getValue()) {
+ log.error("图片所在空间不是团队空间,拒绝握手");
+ return false;
+ }
+ }
+ List permissionList = spaceUserAuthManager.getPermissionList(space, loginUser);
+ if (!permissionList.contains(SpaceUserPermissionConstant.PICTURE_EDIT)) {
+ log.error("用户没有编辑图片的权限,拒绝握手");
+ return false;
+ }
+ // 如果握手成功,设置用户登录信息等属性到 WebSocket 会话的属性 Map中
+ attributes.put("user", loginUser);
+ attributes.put("userId", loginUser.getId());
+ attributes.put("pictureId", Long.valueOf(pictureId)); // 记得转换为 Long 类型
+ }
+ return true;
+ }
+
+ @Override
+ public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
+ }
+}
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEvent.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEvent.java
new file mode 100644
index 0000000..ec79f74
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEvent.java
@@ -0,0 +1,34 @@
+package edu.whut.smilepicturebackend.manager.websocket.disruptor;
+
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditRequestMessage;
+import edu.whut.smilepicturebackend.model.entity.User;
+import lombok.Data;
+import org.springframework.web.socket.WebSocketSession;
+
+/**
+ * 图片编辑事件
+ */
+@Data
+public class PictureEditEvent {
+
+ /**
+ * 消息
+ */
+ private PictureEditRequestMessage pictureEditRequestMessage;
+
+ /**
+ * 当前用户的 session
+ */
+ private WebSocketSession session;
+
+ /**
+ * 当前用户
+ */
+ private User user;
+
+ /**
+ * 图片 id
+ */
+ private Long pictureId;
+
+}
\ No newline at end of file
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventDisruptorConfig.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventDisruptorConfig.java
new file mode 100644
index 0000000..f7eccb3
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventDisruptorConfig.java
@@ -0,0 +1,35 @@
+package edu.whut.smilepicturebackend.manager.websocket.disruptor;
+
+import cn.hutool.core.thread.ThreadFactoryBuilder;
+import com.lmax.disruptor.dsl.Disruptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+
+/**
+ * 图片编辑事件 Disruptor 配置
+ */
+@Configuration
+public class PictureEditEventDisruptorConfig {
+
+ @Resource
+ private PictureEditEventWorkHandler pictureEditEventWorkHandler;
+
+ @Bean("pictureEditEventDisruptor")
+ public Disruptor messageModelRingBuffer() {
+ // 定义 ringBuffer 的大小 ,小了可能数据来不及消费
+ int bufferSize = 1024 * 256;
+ // 创建 disruptor
+ Disruptor disruptor = new Disruptor<>(
+ PictureEditEvent::new,
+ bufferSize,
+ ThreadFactoryBuilder.create().setNamePrefix("pictureEditEventDisruptor").build()
+ );
+ // 设置消费者
+ disruptor.handleEventsWithWorkerPool(pictureEditEventWorkHandler);
+ // 启动 disruptor
+ disruptor.start();
+ return disruptor;
+ }
+}
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventProducer.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventProducer.java
new file mode 100644
index 0000000..caf0869
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventProducer.java
@@ -0,0 +1,53 @@
+package edu.whut.smilepicturebackend.manager.websocket.disruptor;
+
+import com.lmax.disruptor.RingBuffer;
+import com.lmax.disruptor.dsl.Disruptor;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditRequestMessage;
+import edu.whut.smilepicturebackend.model.entity.User;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+
+/**
+ * 图片编辑事件(生产者)
+ */
+@Component
+@Slf4j
+public class PictureEditEventProducer {
+
+ @Resource
+ private Disruptor pictureEditEventDisruptor;
+
+ /**
+ * 发布事件
+ *
+ * @param pictureEditRequestMessage
+ * @param session
+ * @param user
+ * @param pictureId
+ */
+ public void publishEvent(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) {
+ RingBuffer ringBuffer = pictureEditEventDisruptor.getRingBuffer();
+ // 获取到可以防止事件的位置
+ long next = ringBuffer.next();
+ PictureEditEvent pictureEditEvent = ringBuffer.get(next);
+ pictureEditEvent.setPictureEditRequestMessage(pictureEditRequestMessage);
+ pictureEditEvent.setSession(session);
+ pictureEditEvent.setUser(user);
+ pictureEditEvent.setPictureId(pictureId);
+ // 发布事件
+ ringBuffer.publish(next);
+ }
+
+ /**
+ * 优雅停机
+ */
+ @PreDestroy
+ public void destroy() {
+ pictureEditEventDisruptor.shutdown();
+ }
+}
+
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventWorkHandler.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventWorkHandler.java
new file mode 100644
index 0000000..0d24597
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/disruptor/PictureEditEventWorkHandler.java
@@ -0,0 +1,61 @@
+package edu.whut.smilepicturebackend.manager.websocket.disruptor;
+
+import cn.hutool.json.JSONUtil;
+import com.lmax.disruptor.WorkHandler;
+import edu.whut.smilepicturebackend.manager.websocket.PictureEditHandler;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditMessageTypeEnum;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditRequestMessage;
+import edu.whut.smilepicturebackend.manager.websocket.model.PictureEditResponseMessage;
+import edu.whut.smilepicturebackend.model.entity.User;
+import edu.whut.smilepicturebackend.service.UserService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+import javax.annotation.Resource;
+
+/**
+ * 图片编辑事件处理器(消费者)
+ */
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class PictureEditEventWorkHandler implements WorkHandler {
+
+ private final PictureEditHandler pictureEditHandler;
+
+ private final UserService userService;
+
+ @Override
+ public void onEvent(PictureEditEvent pictureEditEvent) throws Exception {
+ PictureEditRequestMessage pictureEditRequestMessage = pictureEditEvent.getPictureEditRequestMessage();
+ WebSocketSession session = pictureEditEvent.getSession();
+ User user = pictureEditEvent.getUser();
+ Long pictureId = pictureEditEvent.getPictureId();
+ // 获取到消息类别
+ String type = pictureEditRequestMessage.getType();
+ PictureEditMessageTypeEnum pictureEditMessageTypeEnum = PictureEditMessageTypeEnum.getEnumByValue(type);
+ // 根据消息类型处理消息
+ switch (pictureEditMessageTypeEnum) {
+ case ENTER_EDIT:
+ pictureEditHandler.handleEnterEditMessage(pictureEditRequestMessage, session, user, pictureId);
+ break;
+ case EXIT_EDIT:
+ pictureEditHandler.handleExitEditMessage(pictureEditRequestMessage, session, user, pictureId);
+ break;
+ case EDIT_ACTION:
+ pictureEditHandler.handleEditActionMessage(pictureEditRequestMessage, session, user, pictureId);
+ break;
+ default:
+ // 其他消息类型,返回错误提示
+ PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
+ pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ERROR.getValue());
+ pictureEditResponseMessage.setMessage("消息类型错误");
+ pictureEditResponseMessage.setUser(userService.getUserVO(user));
+ session.sendMessage(new TextMessage(JSONUtil.toJsonStr(pictureEditResponseMessage)));
+ break;
+ }
+ }
+}
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditActionEnum.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditActionEnum.java
new file mode 100644
index 0000000..703ba24
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditActionEnum.java
@@ -0,0 +1,38 @@
+package edu.whut.smilepicturebackend.manager.websocket.model;
+
+import lombok.Getter;
+
+/**
+ * 图片编辑动作枚举
+ */
+@Getter
+public enum PictureEditActionEnum {
+
+ ZOOM_IN("放大操作", "ZOOM_IN"),
+ ZOOM_OUT("缩小操作", "ZOOM_OUT"),
+ ROTATE_LEFT("左旋操作", "ROTATE_LEFT"),
+ ROTATE_RIGHT("右旋操作", "ROTATE_RIGHT");
+
+ private final String text;
+ private final String value;
+
+ PictureEditActionEnum(String text, String value) {
+ this.text = text;
+ this.value = value;
+ }
+
+ /**
+ * 根据 value 获取枚举
+ */
+ public static PictureEditActionEnum getEnumByValue(String value) {
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+ for (PictureEditActionEnum actionEnum : PictureEditActionEnum.values()) {
+ if (actionEnum.value.equals(value)) {
+ return actionEnum;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditMessageTypeEnum.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditMessageTypeEnum.java
new file mode 100644
index 0000000..012f619
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditMessageTypeEnum.java
@@ -0,0 +1,39 @@
+package edu.whut.smilepicturebackend.manager.websocket.model;
+
+import lombok.Getter;
+
+/**
+ * 图片编辑消息类型枚举
+ */
+@Getter
+public enum PictureEditMessageTypeEnum {
+
+ INFO("发送通知", "INFO"),
+ ERROR("发送错误", "ERROR"),
+ ENTER_EDIT("进入编辑状态", "ENTER_EDIT"),
+ EXIT_EDIT("退出编辑状态", "EXIT_EDIT"),
+ EDIT_ACTION("执行编辑操作", "EDIT_ACTION");
+
+ private final String text;
+ private final String value;
+
+ PictureEditMessageTypeEnum(String text, String value) {
+ this.text = text;
+ this.value = value;
+ }
+
+ /**
+ * 根据 value 获取枚举
+ */
+ public static PictureEditMessageTypeEnum getEnumByValue(String value) {
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+ for (PictureEditMessageTypeEnum typeEnum : PictureEditMessageTypeEnum.values()) {
+ if (typeEnum.value.equals(value)) {
+ return typeEnum;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditRequestMessage.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditRequestMessage.java
new file mode 100644
index 0000000..176a4de
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditRequestMessage.java
@@ -0,0 +1,24 @@
+package edu.whut.smilepicturebackend.manager.websocket.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 图片编辑请求消息
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PictureEditRequestMessage {
+
+ /**
+ * 消息类型,例如 "ENTER_EDIT", "EXIT_EDIT", "EDIT_ACTION"
+ */
+ private String type;
+
+ /**
+ * 光有EDIT_ACTION不够,还要有执行的编辑动作(放大、缩小)
+ */
+ private String editAction;
+}
\ No newline at end of file
diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditResponseMessage.java b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditResponseMessage.java
new file mode 100644
index 0000000..96d1f93
--- /dev/null
+++ b/src/main/java/edu/whut/smilepicturebackend/manager/websocket/model/PictureEditResponseMessage.java
@@ -0,0 +1,35 @@
+package edu.whut.smilepicturebackend.manager.websocket.model;
+
+import edu.whut.smilepicturebackend.model.vo.UserVO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 图片编辑响应消息
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PictureEditResponseMessage {
+
+ /**
+ * 消息类型,例如 "INFO", "ERROR", "ENTER_EDIT", "EXIT_EDIT", "EDIT_ACTION"
+ */
+ private String type;
+
+ /**
+ * 信息
+ */
+ private String message;
+
+ /**
+ * 执行的编辑动作
+ */
+ private String editAction;
+
+ /**
+ * 用户信息
+ */
+ private UserVO user;
+}
\ No newline at end of file
diff --git a/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java b/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java
index bcd7fd3..bf0a3ca 100644
--- a/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java
+++ b/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java
@@ -89,6 +89,10 @@ public class PictureVO implements Serializable {
*/
private Long userId;
+ /**
+ * 空间 id
+ */
+ private Long spaceId;
/**
* 创建时间
diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java
index 658ff0f..b8362e4 100644
--- a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java
+++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java
@@ -119,9 +119,9 @@ public class PictureServiceImpl extends ServiceImpl
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
// 改为使用统一的权限校验
// 校验是否有空间的权限,仅空间管理员才能上传
- if (!loginUser.getId().equals(space.getUserId())) {
- throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "没有空间权限");
- }
+// if (!loginUser.getId().equals(space.getUserId())) {
+// throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "没有空间权限");
+// }
// 校验额度
if (space.getTotalCount() >= space.getMaxCount()) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "空间条数不足");
@@ -138,10 +138,11 @@ public class PictureServiceImpl extends ServiceImpl
if (pictureId != null) {
oldPicture = this.getById(pictureId);
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
+ // 改为使用统一的权限校验
// 仅本人或管理员可编辑图片
- if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
- throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
- }
+// if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
+// throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
+// }
// 校验空间是否一致
// 没传 spaceId,则复用原有图片的 spaceId(这样也兼容了公共图库)
if (spaceId == null) {
@@ -189,7 +190,7 @@ public class PictureServiceImpl extends ServiceImpl
picture.setUserId(loginUser.getId());
picture.setSpaceId(spaceId);
// 转换为标准颜色
- log.info("颜色"+uploadPictureResult.getPicColor());
+// log.info("颜色"+uploadPictureResult.getPicColor());
picture.setPicColor(ColorTransformUtils.getStandardColor(uploadPictureResult.getPicColor()));
// 补充审核参数
this.fillReviewParams(picture, loginUser);