4.29 多人协同编辑图片 websocket Disruptor
This commit is contained in:
parent
6a5fb45cc2
commit
528422fed6
11
pom.xml
11
pom.xml
@ -102,6 +102,17 @@
|
||||
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</dependency>
|
||||
<!-- websocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<!-- 高性能无锁队列 -->
|
||||
<dependency>
|
||||
<groupId>com.lmax</groupId>
|
||||
<artifactId>disruptor</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
|
@ -13,8 +13,14 @@ public class MainController {
|
||||
* 健康检查
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public BaseResponse<String> health() {
|
||||
public BaseResponse<String> health() throws InterruptedException {
|
||||
// new Thread(() -> {
|
||||
// try {
|
||||
// Thread.sleep(100000L);
|
||||
// } catch (InterruptedException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }).start();
|
||||
return ResultUtils.success("ok");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
//已改为编程式鉴权
|
||||
|
@ -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<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();
|
||||
|
||||
// 保存所有连接的会话,key: pictureId, value: 所有正在编辑这张图片的用户会话集合
|
||||
private final Map<Long, Set<WebSocketSession>> 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<WebSocketSession> 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<WebSocketSession> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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("*");
|
||||
}
|
||||
}
|
@ -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<String, Object> 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<String> 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) {
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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<PictureEditEvent> messageModelRingBuffer() {
|
||||
// 定义 ringBuffer 的大小 ,小了可能数据来不及消费
|
||||
int bufferSize = 1024 * 256;
|
||||
// 创建 disruptor
|
||||
Disruptor<PictureEditEvent> disruptor = new Disruptor<>(
|
||||
PictureEditEvent::new,
|
||||
bufferSize,
|
||||
ThreadFactoryBuilder.create().setNamePrefix("pictureEditEventDisruptor").build()
|
||||
);
|
||||
// 设置消费者
|
||||
disruptor.handleEventsWithWorkerPool(pictureEditEventWorkHandler);
|
||||
// 启动 disruptor
|
||||
disruptor.start();
|
||||
return disruptor;
|
||||
}
|
||||
}
|
@ -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<PictureEditEvent> pictureEditEventDisruptor;
|
||||
|
||||
/**
|
||||
* 发布事件
|
||||
*
|
||||
* @param pictureEditRequestMessage
|
||||
* @param session
|
||||
* @param user
|
||||
* @param pictureId
|
||||
*/
|
||||
public void publishEvent(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) {
|
||||
RingBuffer<PictureEditEvent> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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<PictureEditEvent> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -89,6 +89,10 @@ public class PictureVO implements Serializable {
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 空间 id
|
||||
*/
|
||||
private Long spaceId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
|
@ -119,9 +119,9 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
||||
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<PictureMapper, Picture>
|
||||
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<PictureMapper, Picture>
|
||||
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user