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);