智能协同云图库项目教程 - 图片协同编辑 - 后端改造策略模式

目录结构

text
复制代码
websocket/ ├── handler/ │ ├── PictureEditHandler.java │ └── WsHandshakeInterceptor.java ├── model/ │ ├── enums/ │ │ ├── PictureEditActionEnum.java │ │ └── PictureEditMessageTypeEnum.java │ ├── PictureEditRequestMessage.java │ └── PictureEditResponseMessage.java ├── strategy/ │ ├── impl/ │ │ ├── EditActionMessageStrategy.java │ │ ├── EnterEditMessageStrategy.java │ │ ├── ErrorMessageStrategy.java │ │ └── ExitEditMessageStrategy.java │ ├── PictureEditMessageStrategy.java │ └── PictureEditMessageStrategyFactory.java └── util/ ├── PictureEditBroadcaster.java └── PictureEditingStatusManager.java

核心代码实现

策略接口

java
复制代码
/** * @author tt * @date 2025/3/13 09:50 * 图片编辑消息处理策略 */ public interface PictureEditMessageStrategy { /** * 处理图片编辑请求消息 * * @param requestMessage 图片编辑请求消息 * @param session WebSocket会话 * @param user 用户信息 * @param pictureId 图片ID * @throws Exception 处理异常 */ void handle(PictureEditRequestMessage requestMessage, WebSocketSession session, User user, Long pictureId) throws Exception; }

策略实现类

EnterEditMessageStrategy.java

java
复制代码
/** * @author tt * @date 2025/3/13 09:51 * 进入编辑图片状态 */ @Component public class EnterEditMessageStrategy implements PictureEditMessageStrategy { @Resource private UserService userService; @Resource private PictureEditBroadcaster broadcaster; @Resource private PictureEditingStatusManager statusManager; /** * 处理图片编辑请求消息 * * @param requestMessage 图片编辑请求消息 * @param session WebSocket会话 * @param user 用户信息 * @param pictureId 图片ID */ @Override public void handle(PictureEditRequestMessage requestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { // 没有用户正在编辑该图片,才能进入编辑 if (!statusManager.isBeingEdited(pictureId)) { // 设置当前用户为编辑用户 statusManager.setEditingUser(pictureId, user.getId()); PictureEditResponseMessage responseMessage = new PictureEditResponseMessage(); responseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue()); String message = String.format("%s开始编辑图片", user.getUserName()); responseMessage.setMessage(message); responseMessage.setUser(userService.getUserVO(user)); broadcaster.broadcastToPicture(pictureId, responseMessage); } } }

EditActionMessageStrategy.java

java
复制代码
/** * @author tt * @date 2025/3/13 09:52 * 执行编辑操作 */ @Component public class EditActionMessageStrategy implements PictureEditMessageStrategy { @Resource private UserService userService; @Resource private PictureEditBroadcaster broadcaster; @Resource private PictureEditingStatusManager statusManager; /** * 处理图片编辑请求消息 * * @param requestMessage 图片编辑请求消息 * @param session WebSocket会话 * @param user 用户信息 * @param pictureId 图片ID */ @Override public void handle(PictureEditRequestMessage requestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { Long editingUserId = statusManager.getEditingUser(pictureId); String editAction = requestMessage.getEditAction(); PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction); if (actionEnum == null) { return; } // 确认是当前编辑者 if (editingUserId != null && editingUserId.equals(user.getId())) { PictureEditResponseMessage responseMessage = new PictureEditResponseMessage(); responseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue()); String message = String.format("%s执行%s", user.getUserName(), actionEnum.getText()); responseMessage.setMessage(message); responseMessage.setEditAction(editAction); responseMessage.setUser(userService.getUserVO(user)); // 广播给除了当前客户端之外的其他用户,否则会造成重复编辑 broadcaster.broadcastToPicture(pictureId, responseMessage, session); } } }

ExitEditMessageStrategy.java

java
复制代码
/** * @author tt * @date 2025/3/13 09:52 * 退出编辑图片状态 */ @Component public class ExitEditMessageStrategy implements PictureEditMessageStrategy { @Resource private UserService userService; @Resource private PictureEditBroadcaster broadcaster; @Resource private PictureEditingStatusManager statusManager; /** * 处理图片编辑请求消息 * * @param requestMessage 图片编辑请求消息 * @param session WebSocket会话 * @param user 用户信息 * @param pictureId 图片ID */ @Override public void handle(PictureEditRequestMessage requestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { Long editingUserId = statusManager.getEditingUser(pictureId); if (editingUserId != null && editingUserId.equals(user.getId())) { // 移除当前用户的编辑状态 statusManager.removeEditingUser(pictureId); // 构造响应,发送退出编辑的消息通知 PictureEditResponseMessage responseMessage = new PictureEditResponseMessage(); responseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue()); String message = String.format("%s退出编辑图片", user.getUserName()); responseMessage.setMessage(message); responseMessage.setUser(userService.getUserVO(user)); broadcaster.broadcastToPicture(pictureId, responseMessage); } } }

ErrorMessageStrategy.java

java
复制代码
/** * @author tt * @date 2025/3/13 09:53 * 消息类型错误处理 */ @Component public class ErrorMessageStrategy implements PictureEditMessageStrategy { @Resource private UserService userService; /** * 处理图片编辑请求消息 * * @param requestMessage 图片编辑请求消息 * @param session WebSocket会话 * @param user 用户信息 * @param pictureId 图片ID */ @Override public void handle(PictureEditRequestMessage requestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { PictureEditResponseMessage responseMessage = new PictureEditResponseMessage(); responseMessage.setType(PictureEditMessageTypeEnum.ERROR.getValue()); responseMessage.setMessage("消息类型错误"); responseMessage.setUser(userService.getUserVO(user)); session.sendMessage(new TextMessage(JSONUtil.toJsonStr(responseMessage))); } }

策略工厂

java
复制代码
/** * @author tt * @date 2025/3/13 09:54 * 消息处理策略工厂 */ @Component public class PictureEditMessageStrategyFactory { private final Map<String, PictureEditMessageStrategy> strategies = new HashMap<>(); @Resource private EnterEditMessageStrategy enterEditMessageStrategy; @Resource private EditActionMessageStrategy editActionMessageStrategy; @Resource private ExitEditMessageStrategy exitEditMessageStrategy; @Resource private ErrorMessageStrategy errorMessageStrategy; @PostConstruct public void init() { strategies.put(PictureEditMessageTypeEnum.ENTER_EDIT.getValue(), enterEditMessageStrategy); strategies.put(PictureEditMessageTypeEnum.EDIT_ACTION.getValue(), editActionMessageStrategy); strategies.put(PictureEditMessageTypeEnum.EXIT_EDIT.getValue(), exitEditMessageStrategy); } /** * 获取消息处理策略 * * @param type 消息类型 * @return 消息处理策略 */ public PictureEditMessageStrategy getStrategy(String type) { return strategies.getOrDefault(type, errorMessageStrategy); } }

工具类

PictureEditBroadcaster.java

java
复制代码
/** * @author tt * @date 2025/3/13 09:54 */ @Component public class PictureEditBroadcaster { // 保存所有连接的会话,key: pictureId, value: 用户会话集合 private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>(); public void addSession(Long pictureId, WebSocketSession session) { pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet()); pictureSessions.get(pictureId).add(session); } public void removeSession(Long pictureId, WebSocketSession session) { Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId); if (sessionSet != null) { sessionSet.remove(session); if (sessionSet.isEmpty()) { pictureSessions.remove(pictureId); } } } /** * 广播消息给同一张图片的所有用户 * * @param pictureId 图片 ID * @param pictureEditResponseMessage 图片编辑响应消息 * @param excludeSession 排除的会话 */ public void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws Exception { Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId); if (CollUtil.isNotEmpty(sessionSet)) { 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 && excludeSession.equals(session)) { continue; } if (session.isOpen()) { session.sendMessage(textMessage); } } } } // 全部广播 public void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws Exception { broadcastToPicture(pictureId, pictureEditResponseMessage, null); } }

PictureEditingStatusManager.java

java
复制代码
/** * @author tt * @date 2025/3/13 09:55 * 图片编辑状态管理器 */ @Component public class PictureEditingStatusManager { // 每张图片的编辑状态,key: pictureId, value: 当前正在编辑的用户 ID private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>(); public boolean isBeingEdited(Long pictureId) { return pictureEditingUsers.containsKey(pictureId); } public Long getEditingUser(Long pictureId) { return pictureEditingUsers.get(pictureId); } public void setEditingUser(Long pictureId, Long userId) { pictureEditingUsers.put(pictureId, userId); } public void removeEditingUser(Long pictureId) { pictureEditingUsers.remove(pictureId); } }

主WebSocket处理器

java
复制代码
/** * @author tt * @date 2025/3/13 09:37 * 图片编辑 WebSocket 处理器 */ @Component public class PictureEditHandler extends TextWebSocketHandler { @Resource private UserService userService; @Resource private PictureEditBroadcaster broadcaster; @Resource private PictureEditMessageStrategyFactory strategyFactory; /** * 连接建立成功后触发 * * @param session 会话 */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 保存会话到集合中 User user = (User) session.getAttributes().get("user"); Long pictureId = (Long) session.getAttributes().get("pictureId"); broadcaster.addSession(pictureId, 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)); // 广播给同一张图片的用户 broadcaster.broadcastToPicture(pictureId, pictureEditResponseMessage); } /** * 处理文本消息 * * @param session 会话 * @param message 消息 */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 将消息解析为 PictureEditMessage PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class); String type = pictureEditRequestMessage.getType(); // 从 Session 属性中获取公共参数 Map<String, Object> attributes = session.getAttributes(); User user = (User) attributes.get("user"); Long pictureId = (Long) attributes.get("pictureId"); // 获取并执行对应的策略 PictureEditMessageStrategy strategy = strategyFactory.getStrategy(type); strategy.handle(pictureEditRequestMessage, session, user, pictureId); } /** * WebSocket 连接关闭时触发 * * @param session 会话 * @param status 关闭状态 */ @Override public void afterConnectionClosed(WebSocketSession session, @NotNull CloseStatus status) throws Exception { Map<String, Object> attributes = session.getAttributes(); Long pictureId = (Long) attributes.get("pictureId"); User user = (User) attributes.get("user"); // 获取并执行退出编辑策略 PictureEditMessageStrategy exitStrategy = strategyFactory.getStrategy(PictureEditMessageTypeEnum.EXIT_EDIT.getValue()); exitStrategy.handle(null, session, user, pictureId); // 删除会话 broadcaster.removeSession(pictureId, 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)); broadcaster.broadcastToPicture(pictureId, pictureEditResponseMessage); } }

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
tt
作者分享
# 亿级流量点赞系统完结 之前肝完了云图库项目,作为一个还在实习的菜鸟,这次真的学到了太多。我们公司用的就是JDK17和SpringBoot3,看到这个项目正好是讲高并发的,我立马就来劲了,直接开肝 说实话,刚开始看文档的时候还挺忐忑的,怕自己搞不定。但真正上手后发现,很多概念其实在工作中都遇到过,只是之前都是零零散散的,这次终于能系统地串起来了。最让我惊喜的是,我好像突然明白该怎么设计一个高并发系统了,以前看架构图总是一头雾水,现在终于能看懂其中的门道了。 这次项目真的让我get到了不少新技能: 第一次实际运用HeavyKeeper算法和"超级热点"这个概念,原来高并发场景下还有这么巧妙的设计 MQ用得越来越顺手了 最爽的是体验了一把Java21的虚拟线程,跑起来那叫一个丝滑,比传统线程轻量多了 跟着教程学的同时,还能运用平时自己学到的技术和经验,对一些批量处理的地方用线程池去改进,以及分布式锁等 真的要感谢这个项目,让我对开发这件事有了新的理解。以前总觉得能把功能实现就行,现在开始会思考怎么设计更合理、怎么让系统更稳定。 最后必须说,编程导航的教程真的很详细!不像有些教程要么太浅要么太理论,这个项目既有硬核技术又贴近实际开发,学完马上就能用上。 仓库地址:https://gitee.com/tt-y/tt_thumb_backend
13
下载 APP