PageOffice 开发者中心 PageOffice 开发者中心
首页
文档
  • 后端Java手册 (opens new window)
  • 后端.netcore手册 (opens new window)
  • 前端JavaScript手册 (opens new window)
下载
购买 (opens new window)
首页
文档
  • 后端Java手册 (opens new window)
  • 后端.netcore手册 (opens new window)
  • 前端JavaScript手册 (opens new window)
下载
购买 (opens new window)
  • 开始

  • 通用控制

  • Word

  • Excel

  • PDF

  • FileMaker

  • PPT

  • 更多

    • 为什么要安装客户端程序
    • 云文档同时编辑功能存在的问题
    • 与云文档的区别
    • 在Web项目里同时部署Windows版和国产版
    • 文档并发控制
    • 自定义文档并发控制
      • 打开保存文件的执行流程(工作原理)
      • 关于浏览器升级至 Chrome 142 / Edge 143 后导致 PageOffice 客户端误报未安装的说明
    目录

    自定义文档并发控制

    # 自定义文档并发控制

    # 问题背景

    在B/S架构下的办公系统中,用户访问请求都是并发的,也就是说经常会出现同时N个用户对一个服务器页面发出请求,这就有可能出现同一个文档被多个用户同时打开进行编辑的情况,那么,保存时文件就可能出现互相覆盖的问题。为什么会出现互相覆盖呢?举个简单例子,例如A用户先访问页面打开了一个文档开始编辑,这时B用户访问相同的页面打开了同一个文档也开始编辑,B用户可能很快就完成了文档修改工作并且保存到服务器。随后A用户也完成了工作并保存文档到服务器。这时,服务器上的这个文档已经变成了A用户修改的版本,B用户的修改被A用户的保存操作覆盖从而消失了。

    # 两种解决方案

    • 引入工作流模块。文档流转到哪个环节,就由哪个环节的用户去操作,其他用户无法打开此文档,或无法以编辑模式打开此文档。(互联网上有专门的工作流产品,因此本文不对此方案进行介绍。)
    • 采用锁机制实现文档并发控制。在服务器端对文档进行加锁,只有被锁的用户才能打开此文档进行编辑,其他用户无法打开此文档,或无法以编辑模式打开此文档。

    PageOffice V5及以前的版本自带了文档并发控制功能,设置PageOfficeCtrl对象的TimeSlice属性就可以保证同一时间同一篇文档只能由一个人打开,但是此功能仅限于单体Web项目且部署在一台服务器上。由于现在越来越多的项目使用了微服务架构或集群部署,因此就需要开发人员实现自定义的文档并发控制功能。下面我们以一个最简单的文档并发控制方案为例,介绍一下实现自定义的文档并发控制功能的主要步骤:

    1. 数据库设计调整,增加锁状态字段。在文档记录表中增加一个Editor字段,用于记录当前文档是否正被某用户编辑。如果该字段为空,表示文档未被任何用户编辑;如果非空,则字段值应为当前编辑者的用户名。
    2. 打开文件之前发送检查请求。当用户尝试打开文档时,前端应首先向后端发送一个请求,检查文档的Editor字段。
      • 如果Editor字段为空,说明文档当前未被任何用户编辑,可以安全地允许当前用户打开文档,并将当前用户的用户名写入文档记录表中的Editor字段,以此标记文档当前处于被编辑状态。
      • 如果Editor字段非空,即已经有其他用户正在编辑文档,则向用户显示提示信息,告知文档正在被他人编辑,建议稍后再试,或者以只读预览模式打开文档。
    3. 用户完成文档编辑并关闭文档时,前端应当通过Ajax请求通知后端,后端需将文档记录表中对应的Editor字段清空,从而释放文档的编辑锁。

    # 后端代码

    后端验证文档编辑状态的接口(比如:detectCurrentEditor.jsp),代码如下:

    String id = request.getParameter("id");
    String editor = "";
    
    Class.forName("org.sqlite.JDBC");
    String strUrl = "jdbc:sqlite:" + this.getServletContext().getRealPath("BingFa/BingFa.db");
    Connection conn=DriverManager.getConnection(strUrl);
    Statement stmt = conn.createStatement();		
      ResultSet	rs = stmt.executeQuery("select * from doc where id="+id);
    if(rs.next()){
      editor = rs.getString("Editor");
    }
    rs.close();
    stmt.close();
    conn.close();
    
    response.setContentType("application/json");
    response.getWriter().print("{\"editor\":\""+ editor +"\"}"); //返回当前文档的编辑者
    

    后端释放文档编辑锁的接口(比如:clearCurrentEditor.jsp),代码如下:

    String id = request.getParameter("id");
    
    Class.forName("org.sqlite.JDBC");
    String strUrl = "jdbc:sqlite:" + this.getServletContext().getRealPath("BingFa/BingFa.db");
    Connection conn=DriverManager.getConnection(strUrl);
    Statement stmt2 = conn.createStatement();
    stmt2.executeUpdate("Update doc set Editor='' where id="+id); //清空文档的编辑者
    stmt2.close();
    conn.close();
    
    response.setContentType("application/json");
    response.getWriter().print("{\"msg\":\"ok\"}");
    

    # 前端代码

    打开文件之前发送ajax请求到后端接口(比如:detectCurrentEditor.jsp)验证,检查当前文档是否有用户正在编辑。

    // 编辑文件
    function editFile(id){
      var user = loginName; // 假设loginName为当前登录用户的用户名
      // 检查当前文档是否有用户正在编辑
      $.ajax({
        url: 'detectCurrentEditor.jsp?id=' + id, 
        type: 'GET', 
        dataType: 'json', 
        success: function(data) {
          if(data.editor == user || data.editor == ''){
            POBrowser.openWindow('word.jsp?id='+id+'&user='+encodeURIComponent(user) , 'width=1200px;height=800px;');
          }else{
            alert('用户“'+data.editor+'”正在编辑此文档,请稍后重试,或点击“查看”只读打开。');
          }
        },
        error: function(xhr, status, error) {
          console.error('请求失败:', error);
        }
      });
    }
    

    关闭文件时,通过PageOffice的OnBeforeBrowserClosed()事件函数,发送ajax请求到后端接口(比如:clearCurrentEditor.jsp),将文档的Editor字段清空,释放编辑锁。

    // 通知服务器端,用户已停止编辑文档
    function SendCloseMsg(){
      $.ajax({
        url: 'clearCurrentEditor.jsp?id=<%=id%>', 
        type: 'GET', 
        dataType: 'json', 
        success: function(data) {
          console.log('请求成功:', data);
        },
        error: function(xhr, status, error) {
          console.error('请求失败:', error);
        }
      });
    }
    
    function OnBeforeBrowserClosed() {
      // 此处可以执行窗口关闭前需要执行的业务逻辑代码
      if(pageofficectrl.IsDirty){
        if (confirm("提示:文档已被修改,是否继续关闭放弃保存 ?")) {
          SendCloseMsg();
          pageofficectrl.CloseWindow(true);//必须。否则窗口不会关闭。
        } 
      }else{
        SendCloseMsg();
        pageofficectrl.CloseWindow(true);//必须。否则窗口不会关闭。
      }
      
    }
    
    上次更新: 2025/11/07, 13:53:45
    PageOffice | Copyright © 2013-2026 卓正软件 京ICP备12010902号-2 京公网安备 11010502019270号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式