avatar

十六小站

欢迎来到我的个人主页! 期待与您分享我的经验与故事,一起探索技术的无穷可能!

  • 首页
  • NAS专题
  • 关于
Home Onlyoffice编译
文章

Onlyoffice编译

Posted 2025-04-21 Updated 2025-05- 9
By 十六
52~67 min read

下载编译工具

git clone https://github.com/ONLYOFFICE/build_tools.git
//如果要下载指定的历史版本添加--branch参数 如:--branch v7.3.3.60

修改源码

修改QT地址

由于qt的地址有更新,并且官网也不稳定,推荐使用镜像地址:https://www.mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.9/5.9.9/single/

vim /build_tools/tools/linux/automate.py
# 修改QT默认地址

修改连接数

默认是20个链接超过会有提示:

编辑 build_tools/server/Common/sources/contants.js,修改连接数。


1.编辑 build_tools/server/Common/sources/contants.js,修改连接数。

exports.LICENSE_CONNECTIONS = 2000; # 将此处修改你想要的连接数

开启 advancedApi

编辑 server/Common/sources/license.js,设置 advancedApi 为 true,即开启 iframe 端接口命令的能力。

exports.readLicense = async function () {
  ...
  return [
    {
      ...
      advancedApi: true,
      ...
    },
    null
  ];
};

关闭源码更新

vim /build_tools/tools/linux/automate.py

build_tools_params = ["--branch", branch,
                    "--module", modules,
                    "--update", "0",    #此处修改为0,1会拉取最新代码
                    "--qt-dir", os.getcwd() + "/qt_build/Qt-5.9.9"] + params
//也可以在执行命令时添加参数./automate server --update=0,效果一样。

增加 connnector.api 方法

1. 修改 ./sdkjs/common/plugins.js,在 externalConnectorMessage 方法中添加相关代码,如这里添加了关闭和打开历史记录的方法

AscCommon.History.TurnOff()和AscCommon.History.TurnOn()是源码中自带的方法,直接引用即可。

vim /home/sdkjs/common/plugins.js

case "historyTurnOff":
    AscCommon.History.TurnOff();
    break;
case "historyTurnOn":
    AscCommon.History.TurnOn();
    break;

2. connector 添加相关代码,实现 iframe 对方法的监听

执行编译

cd /build_tools/tools/linux

./automate.py server

添加 connector 代码

编辑 build_tools/out/linux_64/onlyoffice/documentserver/web-apps/apps/api/documents/api.js,在 DocsAPI.DocEditor 函数中添加 connetor 相关代码。

(function (m) {
  function n() {
    if (window.crypto && window.crypto.getRandomValues) {
      var a = new Uint16Array(8);
      window.crypto.getRandomValues(a);
      var b = 0;
      function d() {
        return (65536 + a[b++]).toString(16).substring(1);
      }
      return (
        d() + d() + "-" + d() + "-" + d() + "-" + d() + "-" + d() + d() + d()
      );
    }
    function c() {
      return Math.floor(65536 * (1 + Math.random()))
        .toString(16)
        .substring(1);
    }
    return (
      c() + c() + "-" + c() + "-" + c() + "-" + c() + "-" + c() + c() + c()
    );
  }
  function e(a) {
    this.frame = a.frame;
    this.guid = "asc.{" + n() + "}";
    this.isConnected = !1;
    this.callbacks = [];
    this.events = {};
    this.tasks = [];
    this.editorInfo = {};
    this.onMessageBound = this.onMessage.bind(this);
    a.autoconnect && this.connect();
    void 0 === window.Asc && (window.Asc = {});
    void 0 === window.Asc.scope && (window.Asc.scope = {});
  }
  function g(a) {
    this.connector = a;
    this.id = n();
    this.id = this.id.replace(/-/g, "");
    this._events = {};
  }
  e.prototype.onMessage = function (a) {
    if ("string" == typeof a.data) {
      var b = {};
      try {
        b = JSON.parse(a.data);
      } catch (f) {
        b = {};
      }
      if (
        "onExternalPluginMessageCallback" === b.type &&
        ((b = b.data), this.guid === b.guid)
      )
        switch (b.type) {
          case "onMethodReturn":
            0 < this.callbacks.length &&
              (a = this.callbacks.shift()) &&
              a(b.methodReturnData);
            0 < this.tasks.length && this.sendMessage(this.tasks.shift());
            break;
          case "onCommandCallback":
            0 < this.callbacks.length &&
              (a = this.callbacks.shift()) &&
              a(b.commandReturnData);
            0 < this.tasks.length && this.sendMessage(this.tasks.shift());
            break;
          case "onEvent":
            b.eventName &&
              this.events[b.eventName] &&
              this.events[b.eventName].call(this, b.eventData);
            break;
          case "onInfo":
            this.editorInfo = b;
            void 0 !== this.editorInfo.data && delete this.editorInfo.data;
            void 0 !== this.editorInfo.type && delete this.editorInfo.type;
            if (this.editorInfo.theme && this.onThemeChanged)
              this.onThemeChanged(this.editorInfo.theme);
            break;
          case "onTheme":
            this.editorInfo.theme = b.theme;
            if (this.editorInfo.theme && this.onThemeChanged)
              this.onThemeChanged(this.editorInfo.theme);
            break;
          case "onWindowEvent":
            if ("private_window_method" === b.eventName) {
              var c = b.windowID,
                d = this;
              this.executeMethod(
                b.eventData.name,
                b.eventData.params,
                function (f) {
                  d._windows &&
                    d._windows[c] &&
                    d._windows[c].dispatchEvent("on_private_window_method", f);
                }
              );
            } else
              "private_window_command" === b.eventName
                ? ((c = b.windowID),
                  (d = this),
                  this.callCommand(
                    b.eventData.code,
                    function (f) {
                      d._windows &&
                        d._windows[c] &&
                        d._windows[c].dispatchEvent(
                          "on_private_window_command",
                          f
                        );
                    },
                    !1 === b.eventData.isCalc ? !0 : void 0
                  ))
                : this._windows[b.windowID]._oncommand(
                    b.eventName,
                    b.eventData
                  );
            break;
          case "onWindowButton":
            if (this._windows && b.windowID && this._windows[b.windowID])
              if (-1 === b.button) this._windows[b.windowID].close();
              else if (this._windows[b.windowID].onButtonClick)
                this._windows[b.windowID].onButtonClick(b.button);
        }
    }
  };
  e.prototype.sendMessage = function (a) {
    var b = {
      frameEditorId: "iframeEditor",
      type: "onExternalPluginMessage",
      subType: "connector",
    };
    b.data = a;
    b.data.guid = this.guid;
    a = this.frame;
    "string" === typeof a && (a = document.getElementById(this.frame));
    a && a.contentWindow.postMessage(JSON.stringify(b), "*");
  };
  e.prototype.connect = function () {
    this.isConnected
      ? console.log("This connector is already connected")
      : (window.addEventListener
          ? window.addEventListener("message", this.onMessageBound, !1)
          : window.attachEvent &&
            window.attachEvent("onmessage", this.onMessageBound),
        (this.isConnected = !0),
        this.sendMessage({
          type: "register",
        }));
  };
  e.prototype.disconnect = function () {
    this.isConnected
      ? (window.removeEventListener
          ? window.removeEventListener("message", this.onMessageBound, !1)
          : window.detachEvent &&
            window.detachEvent("onmessage", this.onMessageBound),
        (this.isConnected = !1),
        this.sendMessage({
          type: "unregister",
        }),
        this.sendMessage({
          type: "unregister",
        }))
      : console.log("This connector is already disconnected");
  };
  e.prototype.callCommand = function (a, b, c) {
    this.isConnected
      ? (this.callbacks.push(b),
        (a = {
          type: "command",
          recalculate: !0 === c ? !1 : !0,
          data:
            "string" === typeof a
              ? a
              : "var Asc = {}; Asc.scope = " +
                JSON.stringify(window.Asc.scope || {}) +
                "; var scope = Asc.scope; (" +
                a.toString() +
                ")();",
        }),
        1 !== this.callbacks.length ? this.tasks.push(a) : this.sendMessage(a))
      : console.log("Connector is not connected with editor");
  };
  e.prototype.executeMethod = function (a, b, c) {
    this.isConnected
      ? (this.callbacks.push(c),
        (a = {
          type: "method",
          methodName: a,
          data: b,
        }),
        1 !== this.callbacks.length ? this.tasks.push(a) : this.sendMessage(a))
      : console.log("Connector is not connected with editor");
  };
  e.prototype.attachEvent = function (a, b) {
    this.isConnected
      ? ((this.events[a] = b),
        this.sendMessage({
          type: "attachEvent",
          name: a,
        }))
      : console.log("Connector is not connected with editor");
  };
  e.prototype.detachEvent = function (a) {
    this.events[a] &&
      (delete this.events[a],
      this.isConnected
        ? this.sendMessage({
            type: "detachEvent",
            name: a,
          })
        : console.log("Connector is not connected with editor"));
  };
  e.prototype.historyTurnOff = function () {
    this.sendMessage({
      type: "historyTurnOff",
    });
  };
  e.prototype.historyTurnOn = function () {
    this.sendMessage({
      type: "historyTurnOn",
    });
  };
  e.prototype._correctCustomMenuItems = function (a, b) {
    var c = {
      guid: this.guid,
    };
    b.tabs ? (c.tabs = []) : (c.items = []);
    let d = function (p, h) {
      let k = {
        id: void 0 !== h.id ? h.id : n(),
      };
      for (prop in h)
        switch (prop) {
          case "id":
          case "items":
          case "onClick":
            break;
          case "url":
          case "icons":
            k[prop] = new URL(h[prop], location.href).href;
            break;
          default:
            k[prop] = h[prop];
        }
      if (h.items) {
        k.items = [];
        for (var q = 0, r = h.items.length; q < r; q++)
          k.items.push(d(p, h.items[q]));
      } else h.onClick && ((p[a][k.id] = !0), p.attachEvent(k.id, h.onClick));
      return k;
    };
    if (b.tabs) {
      c.tabs = [];
      for (var f = 0, l = b.tabs.length; f < l; f++)
        c.tabs.push(d(this, b.tabs[f]));
    } else
      for (c.items = [], f = 0, l = b.length; f < l; f++)
        c.items.push(d(this, b[f]));
    return c;
  };
  e.prototype._addCustomMenuEvent = function (a) {
    let b = "";
    "contextMenuEvents" == a
      ? (b = "onContextMenuClick")
      : "toolbarMenuEvents" == a && (b = "onToolbarMenuClick");
    this.events[b] ||
      this.attachEvent(
        b,
        function (d) {
          var f = void 0,
            l = d.indexOf("_oo_sep_");
          -1 !== l && ((f = d.substring(l + 8)), (d = d.substring(0, l)));
          this.events[d] && this.events[d].call(this, f);
        }.bind(this)
      );
    if (this[a])
      for (var c in this[a]) this[a].hasOwnProperty(c) && this.detachEvent(c);
    this[a] = {};
  };
  e.prototype.addContextMenuItem = function (a) {
    this._addCustomMenuEvent("contextMenuEvents");
    this.executeMethod("AddContextMenuItem", [
      this._correctCustomMenuItems("contextMenuEvents", a),
    ]);
  };
  e.prototype.updateContextMenuItem = function (a) {
    this.executeMethod("UpdateContextMenuItem", [
      this._correctCustomMenuItems("contextMenuEvents", a),
    ]);
  };
  e.prototype.addToolbarMenuItem = function (a) {
    this._addCustomMenuEvent("toolbarMenuEvents");
    this.executeMethod("AddToolbarMenuItem", [
      this._correctCustomMenuItems("toolbarMenuEvents", a),
    ]);
  };
  g.prototype._register = function () {
    this.connector._windows || (this.connector._windows = {});
    this.connector._windows[this.id] = this;
  };
  g.prototype._unregister = function () {
    this.connector._windows &&
      this.connector._windows[this.id] &&
      delete this.connector._windows[this.id];
  };
  g.prototype.show = function (a) {
    var b = new URL(a.url, location.href).href;
    b = -1 === b.indexOf(".html?") ? b + "?windowID=" : b + "&windowID=";
    b += this.id + "&guid=" + encodeURIComponent(this.connector.guid);
    a.url = b;
    a.icons && (a.icons = new URL(a.icons, location.href).href);
    this._register();
    this.connector.executeMethod("ShowWindow", [this.id, a]);
  };
  g.prototype.activate = function () {
    this.connector.executeMethod("ActivateWindow", [this.id]);
  };
  g.prototype.close = function () {
    this._oncommand("onClose");
    this.connector.executeMethod("CloseWindow", [this.id]);
    this._unregister();
  };
  g.prototype.dispatchEvent = function (a, b) {
    this.connector.executeMethod("SendToWindow", [
      this.id,
      a,
      "object" === typeof b ? JSON.stringify(b) : b,
    ]);
  };
  g.prototype.attachEvent = function (a, b) {
    this._events[a] = b;
  };
  g.prototype.detachEvent = function (a) {
    this._events && this._events[a] && delete this._events[a];
  };
  g.prototype._oncommand = function (a, b) {
    this._events && this._events[a] && this._events[a].call(this, b);
  };
  e.prototype.createWindow = function () {
    return new g(this);
  };
  m.Asc = m.Asc ? m.Asc : {};
  m.Asc.EditorConnector = e;
})(window);

function _createConnector(settings) {
  return new Asc.EditorConnector({
    frame: iframe,
    autoconnect: settings ? settings.autoconnect : true,
  });
}

同时,在 DocsAPI.DocEditor 函数的 return 中添加 createConnector。

return {
  createConnector: _createConnector,
};

编译字体

手动编译常用字体,我这里程序是放在/app/onlyoffice 下的 ,可以根据自己存放地址修改对应路径。

删除原有字体

rm -rf /usr/share/fonts/*
rm -rf /app/onlyoffice/documentserver/core-fonts/*

配置环境变量

export DIR="/app/onlyoffice/documentserver"
export LD_LIBRARY_PATH=/app/onlyoffice/documentserver/server/FileConverter/bin:$LD_LIBRARY_PATH

copy字体

将常用字体copy到/usr/share/fonts/truetype/custom 前面删除了truetype/custom 目录,这里可以手动再创建,再将字体copy到此目录。

mkdir -p /usr/share/fonts/truetype/custom

cp *.woff /usr/share/fonts/truetype/custom/
cp *.ttf /usr/share/fonts/truetype/custom/

编译字体

"$DIR/server/tools/allfontsgen"\
  --input="$DIR/core-fonts"\
  --allfonts-web="$DIR/sdkjs/common/AllFonts.js"\
  --allfonts="$DIR/server/FileConverter/bin/AllFonts.js"\
  --images="$DIR/sdkjs/common/Images"\
  --selection="$DIR/server/FileConverter/bin/font_selection.bin"\
  --output-web="$DIR/fonts"\
  --use-system="true"\
  --use-system-user-fonts="false"

编译后会再fonts目录下生成字体文件,并且在sdkjs/common 文件夹下生成AllFonts.js文件。

问题汇总

icu无法下载

手动进行下载:

cd core/Common/3dParty/icu
wget https://github.com/unicode-org/icu/releases/download/release-58-2/icu4c-58_2-src.tgz
tar -zxvf icu4c-58_2-src.tgz

或者修改/build/build_tools/scripts/core_common/modules/icu.py

24行改为:base.cmd("svn", ["export", "https://github.com/unicode-org/icu/releases/download/release-" + icu_major + "-" + icu_minor + "/icu4c", "./icu", "--non-interactive", "--trust-server-cert"])

将tag改为releases/download/

跨平台编译

修改配置添加下列内容:nano /etc/docker/daemon.json

{
  "experimental": true 
}

重启docker systemctl restart docker

安装 qemu-user-static(宿主机)

sudo apt-get install qemu-user-static binfmt-support

注册 ARM 模拟器(宿主机)

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

运行 ARM 架构的容器

docker run --platform linux/arm64 -it arm64v8/ubuntu bash
  • --platform linux/arm64 指定容器运行在 ARM 架构

  • arm64v8/ubuntu 是 ARM 版本的 Ubuntu 镜像

  1. 在容器内安装编译工具并编译

升级git

apt-get install software-properties-common
add-apt-repository ppa:git-core/ppa
apt update
apt install git

错误:

python升级

(方式一)在线升级

安装依赖

sudo apt update 
sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git 

安装软件

curl https://pyenv.run  | bash 

修改配置

echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> ~/.bashrc 
echo 'eval "$(pyenv init --path)"' >> ~/.bashrc 
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc 
source ~/.bashrc 

安装新版本

pyenv install 3.12.0  # 可替换为最新版本 
pyenv global 3.12.0   # 设为全局默认 

(方式二)在编编译

下载wget并升级

sudo apt update 
sudo apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev wget 

下载源码并解压

wget https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tar.xz  
tar -xf Python-3.12.0.tar.xz  
cd Python-3.12.0 

编译并配置

./configure --enable-optimizations  # 启用优化 
make -j $(nproc)  # 使用所有CPU核心加速编译 
sudo make altinstall  # 避免覆盖系统默认Python V8构建失败

V8问题

构建命令中可以看到v8的输出架构。如果是x86或者64之类的说明架构自动判断有问题 ,需要在v8目录下手动执行下面命令来构建v8

gn gen out.gn/linux_arm64 --args="v8_static_library=true is_component_build=false use_jemalloc=false v8_monolithic=true v8_use_external_startup_data=false use_custom_libcxx=false treat_warnings_as_errors=false target_cpu=\"arm64\" v8_target_cpu=\"arm64\" is_debug=false is_clang=true use_sysroot=false"

修改config文件

//修改为arm架构
platform="linux_arm64"

clang++架构不对

检查发现clang++构建的时候找不到文件 ,但是文件命名存在 ,最终使用file clang++命令查看文件是x86架构的在arm架构下无法识别 。

wget https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-aarch64-linux-gnu.tar.xz

tar xvf clang+llvm-18.1.8-aarch64-linux-gnu.tar.xz

三方地址:

https://github.com/open-onlyoffice/build_tools.git

arm环境下可以尝试其他编译版本:(暂未验证)

https://github.com/bill88t/build_tools-arm64

License:  CC BY 4.0
Share

Further Reading

OLDER

K6+Playwright实现并发测试

NEWER

Recently Updated

  • Onlyoffice编译
  • K6+Playwright实现并发测试
  • 简单规则引擎
  • 在WEB中子线程可以访问Request上下文
  • onlyoffice配置

Trending Tags

Java Docker 前端 中间件 数据库 群晖 unraid

Contents

©2025 十六小站. 陕ICP备2023009742号-2