Prechádzať zdrojové kódy

修改协作文档交互功能

shiyi 3 rokov pred
rodič
commit
9132b11d7e
32 zmenil súbory, kde vykonal 787 pridanie a 117 odobranie
  1. 8 0
      base/src/main/java/com/sp/director/constant/EventConstant.java
  2. 34 0
      core/src/main/java/com/sp/director/config/threadpool/ThreadPoolConfig.java
  3. 8 0
      core/src/main/java/com/sp/director/module/script/dao/ScriptContentVersionHistoryDao.java
  4. 26 0
      core/src/main/java/com/sp/director/module/script/dao/impl/ScriptContentVersionHistoryDaoImpl.java
  5. 2 2
      core/src/main/java/com/sp/director/module/script/entity/ScriptContentVersionHistory.java
  6. 49 15
      core/src/main/java/com/sp/director/module/script/help/ScriptContentVersionOperator.java
  7. 2 2
      core/src/main/java/com/sp/director/module/script/model/LineInfoModel.java
  8. 1 1
      core/src/main/java/com/sp/director/module/script/model/OperatorModel.java
  9. 16 0
      core/src/main/java/com/sp/director/module/script/param/ChapterNewLineParam.java
  10. 38 1
      core/src/main/java/com/sp/director/module/script/service/IScriptContentVersionHistoryService.java
  11. 74 2
      core/src/main/java/com/sp/director/module/script/service/impl/ScriptContentVersionHistoryServiceImpl.java
  12. 48 0
      core/src/main/resources/com/sp/director/module/script/dao/xml/ScriptContentVersionHistoryMapper.xml
  13. 2 0
      logic/src/main/java/com/sp/LogicApplication.java
  14. 0 1
      push/pom.xml
  15. 2 0
      socket/src/main/java/com/sp/SocketApplication.java
  16. 24 0
      socket/src/main/java/com/sp/socket/bo/OperatorNodeBo.java
  17. 28 8
      socket/src/main/java/com/sp/socket/config/ServerSocketConfig.java
  18. 16 0
      socket/src/main/java/com/sp/socket/constant/ParamConstant.java
  19. 2 0
      socket/src/main/java/com/sp/socket/constant/QueueConstant.java
  20. 47 0
      socket/src/main/java/com/sp/socket/handler/NodeChangeHandler.java
  21. 106 0
      socket/src/main/java/com/sp/socket/handler/PersistenceChapterNodeModifyHistoryHandler.java
  22. 13 26
      socket/src/main/java/com/sp/socket/handler/ScriptModifyHandler.java
  23. 33 0
      socket/src/main/java/com/sp/socket/listenner/script/ChapterEditChangeAckListener.java
  24. 36 0
      socket/src/main/java/com/sp/socket/listenner/script/ChapterNewLineNumListener.java
  25. 3 9
      socket/src/main/java/com/sp/socket/listenner/script/ScriptEditConnectListener.java
  26. 5 12
      socket/src/main/java/com/sp/socket/listenner/script/ScriptEditDisConnectListener.java
  27. 34 17
      socket/src/main/java/com/sp/socket/listenner/script/ScriptEditEventListener.java
  28. 1 1
      socket/src/main/java/com/sp/socket/listenner/UserAuthorizationListener.java
  29. 47 3
      socket/src/main/java/com/sp/socket/listenner/script/UserJoinScriptGroupListener.java
  30. 23 0
      socket/src/main/java/com/sp/socket/param/NodeChangeAckParam.java
  31. 23 0
      socket/src/main/java/com/sp/socket/param/ScriptJoinEditGroupParam.java
  32. 36 17
      socket/src/main/java/com/sp/socket/util/ClientUtil.java

+ 8 - 0
base/src/main/java/com/sp/director/constant/EventConstant.java

@@ -12,8 +12,16 @@ public class EventConstant {
 
     public static final String USER_JOIN_EDIT_GROUP = "user_join_edit_group";
 
+    public static final String CHAPTER_EDIT_CHANGE_ACK = "chapter_edit_change_ack";
+
+    public static final String CHAPTER_NEW_LINE_NUM = "chapter_new_line_num";
+
     public static final String NODE_CHANGE_PUSH = "node_change_push";
 
     public static final String SCRIPT_PUBLISH_MODIFY = "script_publish_modify";
 
+
+    // 发送给客户端的事件
+    public static final String CHAPTER_NODE_CHANGE_NOTIFY = "chapter_node_change_notify";
+
 }

+ 34 - 0
core/src/main/java/com/sp/director/config/threadpool/ThreadPoolConfig.java

@@ -0,0 +1,34 @@
+package com.sp.director.config.threadpool;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+@Configuration
+public class ThreadPoolConfig implements AsyncConfigurer {
+
+    @Override
+    @Bean("executorPool")
+    public Executor getAsyncExecutor() {
+        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+        taskExecutor.setCorePoolSize(10);
+        taskExecutor.setMaxPoolSize(50);
+        taskExecutor.setQueueCapacity(100);
+        taskExecutor.setKeepAliveSeconds(60);
+        taskExecutor.setThreadNamePrefix("directorExecutor--");
+        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
+        taskExecutor.setAwaitTerminationSeconds(60);
+        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return taskExecutor;
+    }
+}

+ 8 - 0
core/src/main/java/com/sp/director/module/script/dao/ScriptContentVersionHistoryDao.java

@@ -3,6 +3,8 @@ package com.sp.director.module.script.dao;
 import com.sp.director.module.script.entity.ScriptContentVersionHistory;
 import com.sp.director.mybatis.plus.IBaseDAO;
 
+import java.util.List;
+
 /**
  * <p>
  *  Mapper 接口
@@ -13,4 +15,10 @@ import com.sp.director.mybatis.plus.IBaseDAO;
  */
 public interface ScriptContentVersionHistoryDao extends IBaseDAO<ScriptContentVersionHistory> {
 
+    Long queryLatestLineNum(Long chapterId);
+
+    Long queryLatestSerialNum(Long chapterId);
+
+    List<ScriptContentVersionHistory> queryModifyHistoryRecordOverSerialNum(Long chapterId, Long serialNum);
+
 }

+ 26 - 0
core/src/main/java/com/sp/director/module/script/dao/impl/ScriptContentVersionHistoryDaoImpl.java

@@ -5,9 +5,12 @@ import com.sp.director.module.script.dao.ScriptContentVersionHistoryDao;
 import com.sp.director.module.script.entity.ScriptContentVersionHistory;
 import com.sp.director.mybatis.plus.BaseDAOImpl;
 import org.apache.ibatis.annotations.Mapper;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 /**
  * @Descrption
  * @Author: 十一
@@ -17,9 +20,32 @@ import org.springframework.stereotype.Repository;
 @Repository
 public class ScriptContentVersionHistoryDaoImpl extends BaseDAOImpl<ScriptContentVersionHistoryMapper, ScriptContentVersionHistory> implements ScriptContentVersionHistoryDao {
 
+    @Autowired
+    private ScriptContentVersionHistoryMapper mapper;
+
+    @Override
+    public List<ScriptContentVersionHistory> queryModifyHistoryRecordOverSerialNum(Long chapterId, Long serialNum) {
+        return mapper.queryModifyHistoryRecordOverSerialNum(chapterId,serialNum);
+    }
+
+    @Override
+    public Long queryLatestSerialNum(Long chapterId) {
+        return mapper.queryLatestSerialNum(chapterId);
+    }
+
+    @Override
+    public Long queryLatestLineNum(Long chapterId) {
+        return mapper.queryLatestLineNum(chapterId);
 
+    }
 }
 @Mapper
 @Component
 interface ScriptContentVersionHistoryMapper extends BaseMapper<ScriptContentVersionHistory> {
+
+    Long queryLatestLineNum(Long chapterId);
+
+    Long queryLatestSerialNum(Long chapterId);
+
+    List<ScriptContentVersionHistory> queryModifyHistoryRecordOverSerialNum(Long chapterId, Long serialNum);
 }

+ 2 - 2
core/src/main/java/com/sp/director/module/script/entity/ScriptContentVersionHistory.java

@@ -34,7 +34,7 @@ public class ScriptContentVersionHistory extends Model<ScriptContentVersionHisto
     /**
      * 章节id
      */
-    private Long scriptId;
+    private Long chapterId;
 
     /**
      * 修改的用户id
@@ -59,7 +59,7 @@ public class ScriptContentVersionHistory extends Model<ScriptContentVersionHisto
     /**
      * 行号id
      */
-    private Integer lineId;
+    private Long lineId;
 
     /**
      * 操作类型 新增/删除

+ 49 - 15
core/src/main/java/com/sp/director/module/script/help/ScriptContentVersionOperator.java

@@ -5,6 +5,7 @@ import com.sp.director.exception.ParamException;
 import com.sp.director.ienum.OperationTypeEnum;
 import com.sp.director.module.script.entity.ScriptContentVersionHistory;
 import com.sp.director.module.script.model.LineInfoModel;
+import com.sp.director.module.script.model.OperatorModel;
 import com.sp.director.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 
@@ -41,8 +42,10 @@ public class ScriptContentVersionOperator {
      */
     private Integer docSubscriptionSize = 70;
 
+    private Map<Long, Integer> lineNumMap = new HashMap<>();
+
     /**
-     * 章节行列表,index 为行号 -1
+     * 章节行列表,
      */
     private List<LineInfoModel> chapterLineList = new ArrayList<>();
 
@@ -67,6 +70,31 @@ public class ScriptContentVersionOperator {
     }
 
     /**
+     * 获取整个文档的操作流程
+     */
+    public List<OperatorModel> getAndSortHistoryOperator(){
+        // 将剧本内容按照版本进行分发排序,从小到大
+        contentVersionHistoryList = sortBySerialNum(contentVersionHistoryList);
+        List<OperatorModel> resultList = new ArrayList<>(contentVersionHistoryList.size());
+        for (ScriptContentVersionHistory record : contentVersionHistoryList) {
+            OperatorModel model = new OperatorModel();
+            model.setModel(JSON.parseObject(record.getContent(), LineInfoModel.class));
+            model.setBeforeLineId(record.getBeforeLineId().toString());
+            model.setOperationType(record.getType());
+            model.setSerialNumber(record.getSerialNum());
+            resultList.add(model);
+        }
+        return resultList;
+    }
+
+    public List<ScriptContentVersionHistory> sortBySerialNum(List<ScriptContentVersionHistory> list){
+        return contentVersionHistoryList
+                .stream()
+                .sorted(Comparator.comparing(ScriptContentVersionHistory::getSerialNum))
+                .collect(Collectors.toList());
+    }
+
+    /**
      * 解析出文档内容,以行为单位
      * @return
      */
@@ -75,10 +103,7 @@ public class ScriptContentVersionOperator {
             return Collections.emptyList();
         }
         // 将剧本内容按照版本进行分发排序,从小到大
-        contentVersionHistoryList = contentVersionHistoryList
-                .stream()
-                .sorted(Comparator.comparing(ScriptContentVersionHistory::getSerialNum))
-                .collect(Collectors.toList());
+        contentVersionHistoryList = sortBySerialNum(contentVersionHistoryList);
         int version = 0;
         for (ScriptContentVersionHistory contentHistory : contentVersionHistoryList) {
             String content = contentHistory.getContent();
@@ -89,29 +114,37 @@ public class ScriptContentVersionOperator {
             }
 
             version ++;
-            Integer lineId = contentHistory.getLineId();
-            int index = lineId - 1;
-            LineInfoModel lineInfoModel = chapterLineList.get(index);
+            Long beforeLineId = contentHistory.getBeforeLineId();
+            Integer beforeLineIdIndex = lineNumMap.get(beforeLineId);
+
+            if(beforeLineIdIndex == null){
+                beforeLineIdIndex = -1;
+            }
             serialNum = contentHistory.getSerialNum();
+
             // 判断是新增还是删除
             if (OperationTypeEnum.ADD.equals(contentHistory.getType())) {
-                if( Objects.nonNull(lineInfoModel) ){
-                    log.error("操作记录的顺序异常,剧本{}的新增行{}已存在",chapterId,lineId);
+                Integer index = beforeLineIdIndex + 1;
+                // 判断beforeLineId的记录是否存在
+                if (beforeLineIdIndex != -1 && chapterLineList.get(beforeLineIdIndex) == null) {
+                    log.error("操作记录的顺序异常,新增行{}上一行记录不存在存在",chapterId,contentHistory.getLineId());
                     throw new ParamException();
                 }
                 chapterLineList.add(index,model);
+                lineNumMap.put(contentHistory.getLineId(), index);
             }else{
+                Integer index = lineNumMap.get(contentHistory.getLineId());
+                LineInfoModel lineInfoModel = chapterLineList.get(index);
+                // 判断删除的行是否存在
                 if( Objects.nonNull(lineInfoModel) ){
-                    log.error("操作记录的顺序异常,剧本{}的删除的行{}不存在",chapterId,lineId);
+                    log.error("操作记录的顺序异常,剧本{}的删除的行{}不存在",chapterId,contentHistory.getLineId());
                     throw new ParamException();
                 }
                 chapterLineList.remove(index);
+                lineNumMap.remove(contentHistory.getLineId());
             }
-
-
         }
-
-        return null;
+        return chapterLineList;
     }
 
     /**
@@ -147,4 +180,5 @@ public class ScriptContentVersionOperator {
         LineInfoModel model = JSON.parseObject(str, LineInfoModel.class);
         return model;
     }
+
 }

+ 2 - 2
core/src/main/java/com/sp/director/module/script/model/LineInfoModel.java

@@ -15,8 +15,8 @@ import java.util.Map;
 @Data
 public class LineInfoModel implements Serializable {
 
-    private String lineID;
-    private String roleID;
+    private Long lineId;
+    private String roleId;
     private String content;
     private ContentTypeEnum type;
     private String colorName;

+ 1 - 1
core/src/main/java/com/sp/director/module/script/model/OperatorModel.java

@@ -14,7 +14,7 @@ import java.io.Serializable;
 @Data
 public class OperatorModel implements Serializable {
 
-    private Integer serialNumber;
+    private Long serialNumber;
     private OperationTypeEnum operationType;
     private LineInfoModel model;
     private String beforeLineId;

+ 16 - 0
core/src/main/java/com/sp/director/module/script/param/ChapterNewLineParam.java

@@ -0,0 +1,16 @@
+package com.sp.director.module.script.param;
+
+import lombok.Data;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+@Data
+public class ChapterNewLineParam {
+
+    private Long chapterId;
+}
+

+ 38 - 1
core/src/main/java/com/sp/director/module/script/service/IScriptContentVersionHistoryService.java

@@ -1,6 +1,9 @@
 package com.sp.director.module.script.service;
 
+import com.sp.director.module.script.entity.ScriptContentVersionHistory;
+
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * <p>
@@ -17,7 +20,7 @@ public interface IScriptContentVersionHistoryService extends Serializable {
      * @param chapterId
      * @return
      */
-    public Long cacheChapterSerialNum(Long chapterId,Long serialNum);
+    public Long queryAndCacheChapterSerialNum(Long chapterId,Long serialNum);
 
     /**
      * 保存并刷新章节的版本号
@@ -26,4 +29,38 @@ public interface IScriptContentVersionHistoryService extends Serializable {
      * @return
      */
     public void saveAndFlushChapterSerialNum(Long chapterId, Long serialNum);
+
+
+    /**
+     *  缓存章节的行号
+     * @param chapterId
+     * @return
+     */
+    public Long cacheChapterLineNum(Long chapterId, Long lineNum);
+
+    /**
+     * 保存并刷新章节的行号
+     * @param chapterId
+     * @param lineNum
+     * @return
+     */
+    public void saveAndFlushChapterLineNum(Long chapterId, Long lineNum);
+
+    /**
+     * 异步批量保存
+     * @param saveList
+     */
+    void asyncBatchSaveNode(List<ScriptContentVersionHistory> saveList);
+
+
+    /**
+     * 查询历史记录,操作序号大于serialNum,到latestSerialNum为止,计算版本,如果版本差别较大,则分页查询
+     *
+     * @param chapterId
+     * @param serialNum
+     * @param latestSerialNum
+     * @return
+     */
+    List<ScriptContentVersionHistory> queryLatestRecordByOverSerialNum(Long chapterId, Long serialNum, Long latestSerialNum);
+
 }

+ 74 - 2
core/src/main/java/com/sp/director/module/script/service/impl/ScriptContentVersionHistoryServiceImpl.java

@@ -1,10 +1,18 @@
 package com.sp.director.module.script.service.impl;
 
+import com.sp.director.module.script.dao.ScriptContentVersionHistoryDao;
+import com.sp.director.module.script.entity.ScriptContentVersionHistory;
 import com.sp.director.module.script.service.IScriptContentVersionHistoryService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Cacheable;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
 
 /**
  * <p>
@@ -15,11 +23,21 @@ import org.springframework.stereotype.Service;
  * @since 2021-03-24
  */
 @Service
+@Slf4j
 public class ScriptContentVersionHistoryServiceImpl implements IScriptContentVersionHistoryService {
 
+    @Autowired
+    private ScriptContentVersionHistoryDao scriptContentVersionHistoryDao;
+
     @Override
     @Cacheable(value = "chapter:edit:version:cache",key = "#chapterId")
-    public Long cacheChapterSerialNum(Long chapterId, Long serialNum) {
+    public Long queryAndCacheChapterSerialNum(Long chapterId, Long serialNum) {
+        if(Objects.isNull(serialNum)){
+            serialNum = scriptContentVersionHistoryDao.queryLatestSerialNum(chapterId);
+            if(Objects.isNull(serialNum)){
+                serialNum = 0L;
+            }
+        }
         return serialNum;
     }
 
@@ -27,6 +45,60 @@ public class ScriptContentVersionHistoryServiceImpl implements IScriptContentVer
     @CacheEvict(value="chapter:edit:version:cache",key="#chapterId")
     public void saveAndFlushChapterSerialNum(Long chapterId, Long serialNum) {
         IScriptContentVersionHistoryService service = (IScriptContentVersionHistoryService) AopContext.currentProxy();
-        service.cacheChapterSerialNum(chapterId, serialNum);
+        service.queryAndCacheChapterSerialNum(chapterId, serialNum);
+    }
+
+
+
+    @Override
+    @Cacheable(value = "chapter:edit:linenum:cache",key = "#chapterId")
+    public Long cacheChapterLineNum(Long chapterId, Long lineNum) {
+        if(lineNum == null){
+            // 从数据库中查询
+            Long currentLineNum = scriptContentVersionHistoryDao.queryLatestLineNum(chapterId);
+            if(Objects.isNull(currentLineNum)){
+                currentLineNum = 0L;
+            }
+            return currentLineNum;
+        }
+        return lineNum;
+    }
+
+    @Override
+    @Async("executorPool")
+    public void asyncBatchSaveNode(List<ScriptContentVersionHistory> saveList) {
+        scriptContentVersionHistoryDao.saveBatch(saveList);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<ScriptContentVersionHistory> queryLatestRecordByOverSerialNum(Long chapterId, Long serialNum, Long latestSerialNum) {
+        List<ScriptContentVersionHistory> list = new ArrayList<>();
+        if( Objects.isNull(serialNum) ){
+            return Collections.emptyList();
+        }
+        int limitSize = 100;
+        boolean needPageQuery = latestSerialNum - serialNum > limitSize;
+        List<ScriptContentVersionHistory> queryList = scriptContentVersionHistoryDao.queryModifyHistoryRecordOverSerialNum(chapterId,serialNum);
+        list.addAll(queryList);
+        while(needPageQuery){
+            if(queryList.isEmpty()){
+                log.warn("查询的数据未尚未刷到磁盘");
+                needPageQuery = false;
+            }else{
+                ScriptContentVersionHistory history = queryList.get(queryList.size() - 1);
+                serialNum = history.getSerialNum();
+                queryList = scriptContentVersionHistoryDao.queryModifyHistoryRecordOverSerialNum(chapterId,serialNum);
+                list.addAll(queryList);
+            }
+        }
+        return list;
+    }
+
+    @Override
+    @CacheEvict(value="chapter:edit:linenum:cache",key="#chapterId")
+    public void saveAndFlushChapterLineNum(Long chapterId, Long lineNum) {
+        IScriptContentVersionHistoryService service = (IScriptContentVersionHistoryService) AopContext.currentProxy();
+        service.cacheChapterLineNum(chapterId, lineNum);
     }
 }

+ 48 - 0
core/src/main/resources/com/sp/director/module/script/dao/xml/ScriptContentVersionHistoryMapper.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sp.director.module.script.dao.impl.ScriptContentVersionHistoryMapper">
+
+
+    <select id="queryLatestLineNum" resultType="java.lang.Long" parameterType="java.lang.Long">
+        select max(line_id) lineId
+        from t_script_content_version_history scvh
+        <where>
+            <if test="chapterId != null">
+                and scvh.chapter_id = #{chapterId}
+            </if>
+        </where>
+    </select>
+
+    <select id="queryLatestSerialNum" resultType="java.lang.Long" parameterType="java.lang.Long">
+        select max(serial_num) serialNum
+        from t_script_content_version_history scvh
+        <where>
+            <if test="chapterId != null">
+                and scvh.chapter_id = #{chapterId}
+            </if>
+        </where>
+
+    </select>
+
+    <select id="queryModifyHistoryRecordOverSerialNum"
+            resultType="com.sp.director.module.script.entity.ScriptContentVersionHistory"
+            parameterType="java.lang.Long">
+
+        select
+               h.content,
+               h.serial_num serialNum,
+               h.before_line_id beforeLineId,
+               h.type
+        from t_script_content_version_history h
+        <where>
+                <if test="chapterId != null">
+                    and h.chapter_id = #{chapterId}
+                </if>
+                <if test="serialNum != null">
+                    and h.serial_num <![CDATA[>]]> #{serialNum}
+                </if>
+        </where>
+        order by serial_num asc
+        limit 100;
+    </select>
+</mapper>

+ 2 - 0
logic/src/main/java/com/sp/LogicApplication.java

@@ -7,9 +7,11 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
 
 @SpringBootApplication
 @EnableCaching
+@EnableAsync
 @EnableAspectJAutoProxy(exposeProxy = true)
 @MapperScan(basePackages={"com.sp"}, annotationClass= Mapper.class)
 @Slf4j

+ 0 - 1
push/pom.xml

@@ -13,7 +13,6 @@
     <artifactId>push</artifactId>
     <version>1.0.0</version>
     <name>push</name>
-    <description>push project</description>
 
     <properties>
         <java.version>1.8</java.version>

+ 2 - 0
socket/src/main/java/com/sp/SocketApplication.java

@@ -10,9 +10,11 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
 
 @SpringBootApplication
 @EnableCaching
+@EnableAsync
 @EnableAspectJAutoProxy(exposeProxy = true)
 @MapperScan(basePackages={"com.sp"}, annotationClass= Mapper.class)
 @Slf4j

+ 24 - 0
socket/src/main/java/com/sp/socket/bo/OperatorNodeBo.java

@@ -0,0 +1,24 @@
+package com.sp.socket.bo;
+
+import com.sp.director.module.script.model.OperatorModel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+@Data
+public class OperatorNodeBo extends OperatorModel implements Serializable {
+
+    private Long chapterId;
+
+    private Long modifyUserId;
+
+    private LocalDateTime modifyDateTime;
+
+}

+ 28 - 8
socket/src/main/java/com/sp/socket/config/ServerSocketConfig.java

@@ -3,11 +3,12 @@ package com.sp.socket.config;
 import com.corundumstudio.socketio.Configuration;
 import com.corundumstudio.socketio.SocketIOServer;
 import com.sp.director.constant.EventConstant;
+import com.sp.director.module.script.model.OperatorModel;
+import com.sp.director.module.script.param.ChapterNewLineParam;
 import com.sp.socket.exception.ChatExceptionListener;
-import com.sp.socket.listenner.UserAuthorizationListener;
-import com.sp.socket.listenner.script.ScriptEditConnectListener;
-import com.sp.socket.listenner.script.ScriptEditDisConnectListener;
-import com.sp.socket.listenner.script.ScriptEditEventListener;
+import com.sp.socket.listenner.script.*;
+import com.sp.socket.param.NodeChangeAckParam;
+import com.sp.socket.param.ScriptJoinEditGroupParam;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.stereotype.Component;
@@ -31,6 +32,15 @@ public class ServerSocketConfig {
     @Autowired
     private ScriptEditEventListener scriptEditEventListener;
 
+    @Autowired
+    private UserJoinScriptGroupListener userJoinScriptGroupListener;
+
+    @Autowired
+    private ChapterEditChangeAckListener chapterEditChangeAckListener;
+
+    @Autowired
+    private ChapterNewLineNumListener chapterNewLineNumListener;
+
     /**
      * 配置socket事件
      * @return
@@ -67,12 +77,22 @@ public class ServerSocketConfig {
 
         // 连接事件
         server.addConnectListener(scriptEditConnectListener);
+
+        // 用户报告自己所编辑剧本的事件
+        server.addEventListener(EventConstant.USER_JOIN_EDIT_GROUP, ScriptJoinEditGroupParam.class,userJoinScriptGroupListener);
+
+        // 客户端在协同编辑的时候从服务器获取最新的行号
+        server.addEventListener(EventConstant.CHAPTER_NEW_LINE_NUM, ChapterNewLineParam.class,chapterNewLineNumListener);
+
+        // 剧本修改提交事件
+        server.addEventListener(EventConstant.NODE_CHANGE_EVENT, OperatorModel.class, scriptEditEventListener);
+
+        // 报告收到剧本修改的变更确认信息
+        server.addEventListener(EventConstant.CHAPTER_EDIT_CHANGE_ACK, NodeChangeAckParam.class,chapterEditChangeAckListener);
+
+
         // 断开连接事件
         server.addDisconnectListener(scriptEditDisConnectListener);
-        // 剧本修改提交事件
-        server.addEventListener(EventConstant.NODE_CHANGE_EVENT, Object.class, scriptEditEventListener);
-        // 用户报告自己所编辑剧本的事件
-//        server.addEventListener(EventConstant.USER_JOIN_EDIT_GROUP,Object.class,);
         return server;
     }
 }

+ 16 - 0
socket/src/main/java/com/sp/socket/constant/ParamConstant.java

@@ -0,0 +1,16 @@
+package com.sp.socket.constant;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+public class ParamConstant {
+
+    public static final String USER_ID = "userId";
+
+    public static final String ROOM_ID = "roomId";
+
+    public static final String SERIAL_NUM = "serialNum";
+}

+ 2 - 0
socket/src/main/java/com/sp/socket/constant/QueueConstant.java

@@ -10,4 +10,6 @@ public class QueueConstant {
 
     public static final String SCRIPT_EDIT_QUEUE = "script_edit_queue";
 
+    public static final String CHAPTER_NODE_MODIFY_QUEUE = "chapter_node_modify_queue";
+
 }

+ 47 - 0
socket/src/main/java/com/sp/socket/handler/NodeChangeHandler.java

@@ -0,0 +1,47 @@
+package com.sp.socket.handler;
+
+import com.corundumstudio.socketio.BroadcastOperations;
+import com.corundumstudio.socketio.SocketIOClient;
+import com.corundumstudio.socketio.SocketIOServer;
+import com.sp.director.module.script.entity.ScriptContentVersionHistory;
+import com.sp.socket.util.ClientUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+@Component
+@Slf4j
+public class NodeChangeHandler {
+
+    @Autowired
+    private SocketIOServer server;
+
+    @Async("executorPool")
+    public void compareClientSerialNum(List<ScriptContentVersionHistory> list){
+        for (ScriptContentVersionHistory record : list) {
+            Long chapterId = record.getChapterId();
+            Long serialNum = record.getSerialNum();
+            BroadcastOperations roomOperations = server.getRoomOperations(chapterId.toString());
+            Collection<SocketIOClient> clients = roomOperations.getClients();
+            for (SocketIOClient client : clients) {
+                Long clientSerialNum = ClientUtil.getSerialNum(client);
+                if( Objects.isNull(clientSerialNum) && clientSerialNum < serialNum){
+                    log.info("保存修改记录发现客户端:{}版本{}不是最新,已推送新版本:{}",
+                            ClientUtil.getUserId(client),ClientUtil.getSerialNum(client),serialNum);
+                    ClientUtil.wrapScriptContentVersionHistoryToOperatorModelAndSendChapterNodeChangeNotifyEvent(record,client);
+                }
+            }
+        }
+    }
+}

+ 106 - 0
socket/src/main/java/com/sp/socket/handler/PersistenceChapterNodeModifyHistoryHandler.java

@@ -0,0 +1,106 @@
+package com.sp.socket.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.sp.director.module.script.entity.ScriptContentVersionHistory;
+import com.sp.director.module.script.service.IScriptContentVersionHistoryService;
+import com.sp.socket.bo.OperatorNodeBo;
+import com.sp.socket.constant.QueueConstant;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @Descrption 持久化章节的变更信息
+ * @Author: 十一
+ * @Date: 2021/3/28
+ * @Version V1.0
+ **/
+@Component
+@Slf4j
+// TODO
+public class PersistenceChapterNodeModifyHistoryHandler implements Runnable{
+
+    private List<ScriptContentVersionHistory> persistenceNodeList = new ArrayList<>(30);
+
+    private Long lastSaveDateTime = System.currentTimeMillis();
+
+    public PersistenceChapterNodeModifyHistoryHandler(){
+        Executors.newSingleThreadScheduledExecutor()
+                .scheduleAtFixedRate(this, 1, 3, TimeUnit.SECONDS);
+    }
+
+    private Lock lock = new ReentrantLock();
+
+
+    /**
+     * 记录保存的长度
+     * 当队列的长度达到20的时候,批量保存记录
+     * 当距离上次保存达到三秒,也会保存,满足其一即可
+     */
+    private Integer saveRecordSizeThreshold = 20;
+
+    // 记录保存的间隔时间 ms
+    private Integer saveRecordTimeThreshold = 3000;
+
+    @Autowired
+    private IScriptContentVersionHistoryService scriptContentVersionHistoryService;
+
+    // 保存记录
+    @JmsListener(destination = QueueConstant.CHAPTER_NODE_MODIFY_QUEUE,
+            containerFactory = "jmsListenerContainerQueue")
+    @Transactional
+    public void handlerNodeChangePersistence(String message) throws InterruptedException {
+        OperatorNodeBo bo = JSON.parseObject(message, OperatorNodeBo.class);
+        ScriptContentVersionHistory history = wrapChapterNodeHistory(bo);
+        try{
+            lock.lock();
+            persistenceNodeList.add(history);
+            Long now = System.currentTimeMillis();
+            while( persistenceNodeList.size() >= saveRecordSizeThreshold ){
+                saveRecord(now);
+            }
+        }finally {
+            lock.unlock();
+        }
+
+    }
+
+    public ScriptContentVersionHistory wrapChapterNodeHistory(OperatorNodeBo bo){
+        ScriptContentVersionHistory history = new ScriptContentVersionHistory();
+        history.setChapterId(bo.getChapterId());
+        history.setContent(JSON.toJSONString(bo.getModel()));
+        history.setType(bo.getOperationType());
+        history.setBeforeLineId(Long.valueOf(bo.getBeforeLineId()));
+        history.setSerialNum(bo.getSerialNumber());
+        history.setLineId(bo.getModel().getLineId());
+        history.setModifyUserId(bo.getModifyUserId());
+        history.setModifyTime(bo.getModifyDateTime());
+        return history;
+    }
+
+    @Override
+    public void run() {
+        Long now = System.currentTimeMillis();
+        if (lastSaveDateTime + saveRecordTimeThreshold >= now) {
+            log.info("定时任务保存记录保存数据");
+            saveRecord(now);
+        }
+    }
+
+    public void saveRecord(Long now){
+        List<ScriptContentVersionHistory> saveList = persistenceNodeList;
+        persistenceNodeList = new ArrayList<>(30);
+        lastSaveDateTime = now;
+        scriptContentVersionHistoryService.asyncBatchSaveNode(saveList);
+
+    }
+}

+ 13 - 26
socket/src/main/java/com/sp/socket/handler/ScriptModifyHandler.java

@@ -5,10 +5,12 @@ import com.corundumstudio.socketio.BroadcastOperations;
 import com.corundumstudio.socketio.SocketIOServer;
 import com.sp.director.constant.EventConstant;
 import com.sp.director.diff.diff_match_patch;
-import com.sp.director.module.script.dto.ScriptContentDTO;
+import com.sp.director.module.script.model.OperatorModel;
 import com.sp.director.module.script.service.IScriptContentService;
+import com.sp.director.utils.ActiveMQUtil;
+import com.sp.socket.bo.OperatorNodeBo;
 import com.sp.socket.constant.QueueConstant;
-import com.sp.socket.param.NodeData;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jms.annotation.JmsListener;
 import org.springframework.stereotype.Component;
@@ -33,13 +35,12 @@ public class ScriptModifyHandler {
     //
     @JmsListener(destination = QueueConstant.SCRIPT_EDIT_QUEUE,containerFactory = "jmsListenerContainerQueue",selector="JMSXGroupID='group0'")
     public void group0(String message) throws InterruptedException {
-        System.out.println(message);
+        doMessageHandle(message);
     }
 
     @JmsListener(destination = QueueConstant.SCRIPT_EDIT_QUEUE,containerFactory = "jmsListenerContainerQueue",selector="JMSXGroupID='group1'")
     public void group1(String message){
         doMessageHandle(message);
-        System.out.println(message);
     }
 
     @JmsListener(destination = QueueConstant.SCRIPT_EDIT_QUEUE,containerFactory = "jmsListenerContainerQueue",selector="JMSXGroupID='group2'")
@@ -50,7 +51,6 @@ public class ScriptModifyHandler {
     @JmsListener(destination = QueueConstant.SCRIPT_EDIT_QUEUE,containerFactory = "jmsListenerContainerQueue",selector="JMSXGroupID='group3'")
     public void group3(String message){
         doMessageHandle(message);
-        System.out.println(message);
     }
 
 
@@ -58,32 +58,19 @@ public class ScriptModifyHandler {
     @JmsListener(destination = QueueConstant.SCRIPT_EDIT_QUEUE,containerFactory = "jmsListenerContainerQueue",selector="JMSXGroupID='group4'")
     public void group4(String message){
         doMessageHandle(message);
-        System.out.println(message);
     }
 
 
     private void doMessageHandle(String message){
-        NodeData nodeData = JSON.parseObject(message, NodeData.class);
-        ScriptContentDTO document = scriptContentService.getDoc(nodeData.getChapterId(),nodeData.getUserId());
-        // 合并内容
-        Object[] objects = diff.patch_apply(nodeData.getPatchList(), document.getContent());
-        String newDoc = objects[0].toString();
-        document.addVersion();
-        document.setContent(newDoc);
-        document.setScriptId(nodeData.getScriptId());
-        document.setId(nodeData.getChapterId());
-        scriptContentService.updateDoc(nodeData.getUserId(),document);
+        OperatorNodeBo bo = JSON.parseObject(message, OperatorNodeBo.class);
+        OperatorModel model = new OperatorModel();
+        BeanUtils.copyProperties(bo,model);
 
         // 任务分发,找到当前房间的队列
-        BroadcastOperations roomOperations = socketServer.getRoomOperations(nodeData.getChapterId().toString());
-        roomOperations.sendEvent(EventConstant.NODE_CHANGE_PUSH,nodeData);
-        // TODO
-        // 判断当前剧本是否发布过
-//        Boolean publish = scriptPublishService.checkHasPublish(nodeData.getScriptId(), nodeData.getChapterId());
-//        if(publish){
-//            // 发送MQ消息去更新剧本发布状态
-//            ScriptModifyBO bo = new ScriptModifyBO();
-//            roomOperations.sendEvent(EventConstant.SCRIPT_PUBLISH_MODIFY,bo);
-//        }
+        BroadcastOperations roomOperations = socketServer.getRoomOperations(bo.getChapterId().toString());
+        roomOperations.sendEvent(EventConstant.NODE_CHANGE_PUSH,model);
+
+        // 将记录投放到队列中,刷盘
+        ActiveMQUtil.sendQueueMessage(QueueConstant.CHAPTER_NODE_MODIFY_QUEUE,bo);
     }
 }

+ 33 - 0
socket/src/main/java/com/sp/socket/listenner/script/ChapterEditChangeAckListener.java

@@ -0,0 +1,33 @@
+package com.sp.socket.listenner.script;
+
+import com.corundumstudio.socketio.AckRequest;
+import com.corundumstudio.socketio.SocketIOClient;
+import com.corundumstudio.socketio.listener.DataListener;
+import com.sp.socket.constant.ParamConstant;
+import com.sp.socket.param.NodeChangeAckParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/24
+ * @Version V1.0
+ **/
+@Component
+@Slf4j
+public class ChapterEditChangeAckListener implements DataListener<NodeChangeAckParam> {
+
+    /**
+     * 客户端收到推送后的确认信息
+     * @param client
+     * @param data
+     * @param ackSender
+     * @throws Exception
+     */
+    @Override
+    public void onData(SocketIOClient client, NodeChangeAckParam data, AckRequest ackSender) throws Exception {
+        client.set(ParamConstant.SERIAL_NUM,data.getAckSerialNum());
+        log.info("更新用户:[{}] serialNum:[{}]",data.getUserId(),data.getAckSerialNum());
+    }
+}

+ 36 - 0
socket/src/main/java/com/sp/socket/listenner/script/ChapterNewLineNumListener.java

@@ -0,0 +1,36 @@
+package com.sp.socket.listenner.script;
+
+import com.corundumstudio.socketio.AckRequest;
+import com.corundumstudio.socketio.SocketIOClient;
+import com.corundumstudio.socketio.listener.DataListener;
+import com.sp.director.module.script.param.ChapterNewLineParam;
+import com.sp.director.module.script.service.IScriptContentVersionHistoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/24
+ * @Version V1.0
+ **/
+@Component
+public class ChapterNewLineNumListener implements DataListener<ChapterNewLineParam> {
+
+    @Autowired
+    private IScriptContentVersionHistoryService scriptContentVersionHistoryService;
+
+    /**
+     * 客户端收到推送后的确认信息
+     * @param client
+     * @param data
+     * @param ackSender
+     * @throws Exception
+     */
+    @Override
+    public void onData(SocketIOClient client, ChapterNewLineParam data, AckRequest ackSender) throws Exception {
+        Long lineNum = scriptContentVersionHistoryService.cacheChapterLineNum(data.getChapterId(), null);
+        lineNum ++;
+        scriptContentVersionHistoryService.saveAndFlushChapterLineNum(data.getChapterId(), lineNum);
+    }
+}

+ 3 - 9
socket/src/main/java/com/sp/socket/listenner/script/ScriptEditConnectListener.java

@@ -2,9 +2,8 @@ package com.sp.socket.listenner.script;
 
 import com.corundumstudio.socketio.SocketIOClient;
 import com.corundumstudio.socketio.listener.ConnectListener;
-import com.sp.director.utils.StringUtil;
 import com.sp.socket.auth.UserContext;
-import com.sp.socket.util.ClientUtil;
+import com.sp.socket.constant.ParamConstant;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
@@ -25,12 +24,7 @@ public class ScriptEditConnectListener implements ConnectListener {
     @Override
     public void onConnect(SocketIOClient client) {
         Long userId = UserContext.getUserId();
-        log.error("新用户连接{}",client.getRemoteAddress());
-        // 根据剧本id将客户端分到不同的房间
-        String roomId = ClientUtil.getRoomId(client);
-        if(StringUtil.isNotBlank(roomId)){
-            client.joinRoom(roomId);
-            log.info("用户{}连接", ClientUtil.getUserId(client));
-        }
+        log.info("新用户:[{}]连接",userId);
+        client.set(ParamConstant.USER_ID,userId);
     }
 }

+ 5 - 12
socket/src/main/java/com/sp/socket/listenner/script/ScriptEditDisConnectListener.java

@@ -1,12 +1,9 @@
 package com.sp.socket.listenner.script;
 
 import com.corundumstudio.socketio.SocketIOClient;
-import com.corundumstudio.socketio.SocketIOServer;
 import com.corundumstudio.socketio.listener.DisconnectListener;
-import com.sp.socket.util.ClientUtil;
+import com.sp.socket.constant.ParamConstant;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
 /**
@@ -19,19 +16,15 @@ import org.springframework.stereotype.Component;
 @Slf4j
 public class ScriptEditDisConnectListener implements DisconnectListener {
 
-    @Autowired
-    @Lazy
-    private SocketIOServer server;
-
     /**
      * 断开连接,将客户端从房间中移除
      * @param client
      */
     @Override
     public void onDisconnect(SocketIOClient client) {
-        String roomId = ClientUtil.getRoomId(client);
-        // 测试用户断开连接是否回自动离开房间
-//        client.leaveRoom(roomId);
-        log.info("用户{}断开连接",ClientUtil.getRoomId(client));
+        String roomId = client.get(ParamConstant.ROOM_ID);
+        Long userId = (Long) client.get(ParamConstant.USER_ID);
+        client.leaveRoom(roomId);
+        log.info("用户[{}]断开连接,离开房间:[{}]",userId,roomId);
     }
 }

+ 34 - 17
socket/src/main/java/com/sp/socket/listenner/script/ScriptEditEventListener.java

@@ -1,16 +1,22 @@
 package com.sp.socket.listenner.script;
 
-import com.alibaba.fastjson.JSON;
 import com.corundumstudio.socketio.AckRequest;
 import com.corundumstudio.socketio.SocketIOClient;
 import com.corundumstudio.socketio.listener.DataListener;
+import com.sp.director.module.script.model.OperatorModel;
+import com.sp.director.module.script.service.IScriptContentVersionHistoryService;
 import com.sp.director.module.script.service.ScriptNodeEditService;
-import com.sp.socket.auth.UserContext;
-import com.sp.socket.param.NodeData;
+import com.sp.director.utils.ActiveMQUtil;
+import com.sp.socket.bo.OperatorNodeBo;
+import com.sp.socket.constant.QueueConstant;
+import com.sp.socket.util.ClientUtil;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
+
 /**
  * @Descrption 剧本编辑监听事件
  * @Author: 十一
@@ -19,26 +25,37 @@ import org.springframework.stereotype.Component;
  **/
 @Component
 @Slf4j
-public class ScriptEditEventListener implements DataListener<Object> {
+public class ScriptEditEventListener implements DataListener<OperatorModel> {
 
     @Autowired
     private ScriptNodeEditService scriptNodeEditService;
 
+    @Autowired
+    private IScriptContentVersionHistoryService scriptContentVersionHistoryService;
+
+    /**
+     * 收到客户端更改剧本信息
+     * @param client
+     * @param data
+     * @param request
+     * @throws Exception
+     */
     @Override
-    public void onData(SocketIOClient client, Object data, AckRequest request) throws Exception {
-//        NodeData nodeData = warpNodeData(data);
+    public void onData(SocketIOClient client, OperatorModel data, AckRequest request) throws Exception {
+
+        Long chapterId = Long.valueOf( ClientUtil.getChapterId(client) );
+        Long userId = ClientUtil.getUserId(client);
+        Long serialNum = scriptContentVersionHistoryService.queryAndCacheChapterSerialNum(chapterId, null);
+        serialNum++;
+        scriptContentVersionHistoryService.saveAndFlushChapterLineNum(chapterId,serialNum);
+        data.setSerialNumber(serialNum);
+        OperatorNodeBo bo = new OperatorNodeBo();
+        BeanUtils.copyProperties(data,bo);
+        bo.setChapterId(chapterId);
+        bo.setModifyUserId(userId);
+        bo.setModifyDateTime(LocalDateTime.now());
+        ActiveMQUtil.sendQueueMessage(QueueConstant.SCRIPT_EDIT_QUEUE, bo);
         log.info("收到客户端连接信息");
-        Long userId = UserContext.getUserId();
-        // 将数据放入MQ
-//        String groupName = MessageGroupNameSupport.getInstance().getGroupName(nodeData.getChapterId().toString());
-//        // 根据节点的chapterId计算出同一个groupName,保证同一个chapter内容在编辑的时候保证消息的顺序
-//        ActiveMQUtil.sendGroupQueueMessage(QueueConstant.SCRIPT_EDIT_QUEUE,groupName,data);
         request.sendAckData("success");
     }
-
-    private NodeData warpNodeData(Object data) {
-        String s = JSON.toJSONString(data);
-//        NodeData nodeData = JSON.parseObject(s, NodeData.class);
-        return null;
-    }
 }

+ 1 - 1
socket/src/main/java/com/sp/socket/listenner/UserAuthorizationListener.java

@@ -1,4 +1,4 @@
-package com.sp.socket.listenner;
+package com.sp.socket.listenner.script;
 
 import com.corundumstudio.socketio.AuthorizationListener;
 import com.corundumstudio.socketio.HandshakeData;

+ 47 - 3
socket/src/main/java/com/sp/socket/listenner/script/UserJoinScriptGroupListener.java

@@ -1,8 +1,23 @@
 package com.sp.socket.listenner.script;
 
+import com.alibaba.fastjson.JSON;
 import com.corundumstudio.socketio.AckRequest;
 import com.corundumstudio.socketio.SocketIOClient;
 import com.corundumstudio.socketio.listener.DataListener;
+import com.sp.director.constant.EventConstant;
+import com.sp.director.module.script.entity.ScriptContentVersionHistory;
+import com.sp.director.module.script.model.LineInfoModel;
+import com.sp.director.module.script.model.OperatorModel;
+import com.sp.director.module.script.service.IScriptContentVersionHistoryService;
+import com.sp.socket.constant.ParamConstant;
+import com.sp.socket.param.ScriptJoinEditGroupParam;
+import com.sp.socket.util.ClientUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Objects;
 
 /**
  * @Descrption
@@ -10,17 +25,46 @@ import com.corundumstudio.socketio.listener.DataListener;
  * @Date: 2021/3/24
  * @Version V1.0
  **/
-public class UserJoinScriptGroupListener implements DataListener<Object> {
+@Component
+@Slf4j
+public class UserJoinScriptGroupListener implements DataListener<ScriptJoinEditGroupParam> {
+
+    @Autowired
+    private IScriptContentVersionHistoryService scriptContentVersionHistoryService;
 
     /**
-     * 标记用户编辑的剧本
+     * 标记用户编辑的剧本,
+     * 判断用户的版本是否是最新版,如果不是,将最新的变更记录推送给当前用户
      * @param client
      * @param data
      * @param ackSender
      * @throws Exception
      */
     @Override
-    public void onData(SocketIOClient client, Object data, AckRequest ackSender) throws Exception {
+    public void onData(SocketIOClient client, ScriptJoinEditGroupParam data, AckRequest ackSender) throws Exception {
+        if( Objects.isNull(data.getChapterId())){
+            log.error("用户加入协作组参数异常,chapterId为空");
+            return;
+        }
+        Long chapterId = ClientUtil.getChapterId(client);
+        Long userId = (Long) client.get(ParamConstant.USER_ID);
+        client.set(ParamConstant.SERIAL_NUM,data.getSerialNum());
+        log.info("用户:[{}]加入剧本:[{}]协作组",userId,data.getChapterId());
+        client.joinRoom(data.getChapterId());
+        client.set(ParamConstant.ROOM_ID,data.getChapterId());
+        Long serialNum = data.getSerialNum();
+        if(Objects.isNull(serialNum)){
+            log.error("用户加入协作组参数异常,serialNum为空");
+            return;
+        }
+        Long latestSerialNum = scriptContentVersionHistoryService.queryAndCacheChapterSerialNum(chapterId, null);
+        if( serialNum < latestSerialNum ){
+            List<ScriptContentVersionHistory> list =
+                    scriptContentVersionHistoryService.queryLatestRecordByOverSerialNum(chapterId,serialNum,latestSerialNum);
+            for (ScriptContentVersionHistory record : list) {
+                ClientUtil.wrapScriptContentVersionHistoryToOperatorModelAndSendChapterNodeChangeNotifyEvent(record, client);
 
+            }
+        }
     }
 }

+ 23 - 0
socket/src/main/java/com/sp/socket/param/NodeChangeAckParam.java

@@ -0,0 +1,23 @@
+package com.sp.socket.param;
+
+import lombok.Data;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+@Data
+public class NodeChangeAckParam {
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 确认的版本号
+     */
+    private Long ackSerialNum;
+}

+ 23 - 0
socket/src/main/java/com/sp/socket/param/ScriptJoinEditGroupParam.java

@@ -0,0 +1,23 @@
+package com.sp.socket.param;
+
+import lombok.Data;
+
+/**
+ * @Descrption
+ * @Author: 十一
+ * @Date: 2021/3/25
+ * @Version V1.0
+ **/
+@Data
+public class ScriptJoinEditGroupParam {
+
+    /**
+     * 章节id
+     */
+    private String chapterId;
+
+    /**
+     * 用户编辑版本
+     */
+    private Long serialNum;
+}

+ 36 - 17
socket/src/main/java/com/sp/socket/util/ClientUtil.java

@@ -1,6 +1,14 @@
 package com.sp.socket.util;
 
+import com.alibaba.fastjson.JSON;
 import com.corundumstudio.socketio.SocketIOClient;
+import com.sp.director.constant.EventConstant;
+import com.sp.director.module.script.entity.ScriptContentVersionHistory;
+import com.sp.director.module.script.model.LineInfoModel;
+import com.sp.director.module.script.model.OperatorModel;
+import com.sp.socket.constant.ParamConstant;
+
+import java.util.Objects;
 
 /**
  * @Descrption
@@ -10,32 +18,43 @@ import com.corundumstudio.socketio.SocketIOClient;
  **/
 public class ClientUtil {
 
-    public static String getKey(SocketIOClient client){
-        String userId = client.getHandshakeData().getSingleUrlParam("userId");
-        String deviceToken = client.getHandshakeData().getSingleUrlParam("deviceToken");
-//        if(StringUtil.isBlank(userId) || StringUtil.isBlank(deviceToken)){
-//            throw new ParamException();
-//        }
-        return userId + ":" + deviceToken;
-    }
-
     /**
-     * 将用户的剧本作为房间号
+     * 获取当前用户
      * @param client
      * @return
      */
-    public static String getRoomId(SocketIOClient client){
-        String rootId = client.getHandshakeData().getSingleUrlParam("scriptId");
-        return rootId;
+    public static Long getUserId(SocketIOClient client){
+        Long userId = (Long) client.get(ParamConstant.USER_ID);
+        return userId;
     }
 
     /**
-     * 获取用户id
+     * 获取当前用户编辑的章节
      * @param client
      * @return
      */
-    public static String getUserId(SocketIOClient client){
-        String userId = client.getHandshakeData().getSingleUrlParam("userId");
-        return userId;
+    public static Long getChapterId(SocketIOClient client){
+        Long chapterId = (Long) client.get(ParamConstant.ROOM_ID);
+        return chapterId;
+    }
+
+    public static Long getSerialNum(SocketIOClient client){
+        Object data = client.get(ParamConstant.SERIAL_NUM);
+        if (Objects.isNull(data)) {
+            Long serialNum = (Long) data;
+            return serialNum;
+        }
+        return null;
+    }
+
+    public static void wrapScriptContentVersionHistoryToOperatorModelAndSendChapterNodeChangeNotifyEvent(
+            ScriptContentVersionHistory record,
+            SocketIOClient client) {
+        OperatorModel model = new OperatorModel();
+        model.setModel(JSON.parseObject(record.getContent(), LineInfoModel.class));
+        model.setBeforeLineId(record.getBeforeLineId().toString());
+        model.setOperationType(record.getType());
+        model.setSerialNumber(record.getSerialNum());
+        client.sendEvent(EventConstant.CHAPTER_NODE_CHANGE_NOTIFY,model);
     }
 }