ElFormItem使用v-if导致校验异常问题

环境信息

1
2
vue: `2.6.14`
element-ui: `2.15.12`

问题描述

当使用v-if控制多个ElFormItem显隐时,表单校验不生效。

不生效体现在两点:

  • 1.使用elform.validate显式调用无效
  • 2.blur/change无法触发的校验

这里有几个关键:

  • 使用v-if控制
  • 多个ElFormItem
  • 先展示的是不进行校验的ElFormItem
  • 不进行校验的ElFormItem数量多余进行校验的ElFormItem

原因

使用elform.validate显式调用无效

先展示的ElFormItem不带prop导致mounted阶段没有向ElForm注册自身示例.

v-if组件缓存,导致显式调用elForm.validate找不到后面展示的ElFormItem示例

针对这个问题直接在el-form-item标签上添加prop即可解决

blur/change无法触发的校验

v-if组件缓存,导致复用了先前展示的ElFormItem实例,而由于先前展示的ElFormItem是非必填的,
所以复用的实例并没有绑定el.form.blurel.form.change事件(ElFormItemaddValidateEvents方法),进而导致blur/change无法触发的校验

针对这个问题直接在el-form-item标签上添加key即可解决

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<template>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
label-width="100px"
>
<!-- <el-form-item
label="活动名称2"
prop="name2"
key="name2"
v-if="show2"
>
<el-input v-model="ruleForm.name2" />
</el-form-item>
<el-form-item
label="活动名称5"
prop="name5"
key="name5"
v-if="show2"
>
<el-input v-model="ruleForm.name5" />
</el-form-item> -->
<el-form-item
label="活动名称2"
prop="name2"
v-if="show2"
>
<el-input v-model="ruleForm.name2" />
</el-form-item>
<el-form-item
label="活动名称5"
prop="name5"
v-if="show2"
>
<el-input v-model="ruleForm.name5" />
</el-form-item>
<el-form-item
label="活动名称3"
prop="name3"
v-if="show34"
>
<el-input v-model="ruleForm.name3" />
</el-form-item>
<el-form-item
label="活动名称4"
prop="name4"
v-if="show34"
>
<el-input v-model="ruleForm.name4" />
</el-form-item>
<el-button
type="primary"
@click="show2=!show2;show34=!show34"
>
SHOW2-SHOW34
</el-button>
<el-button
type="primary"
@click="submitForm('ruleForm')"
>
提交
</el-button>
<el-button @click="resetForm('ruleForm')">
重置
</el-button>
</el-form>
</template>

<script>
export default {
name: 'Root',
data () {
return {
show2: true,
show34: false,
ruleForm: {
name1: '',
name2: '',
name3: '',
name4: ''
},
rules: {
name3: [
{ required: true, message: '请输入活动名称3', trigger: 'blur' }
],
name4: [
{ required: true, message: '请输入活动名称4', trigger: 'blur' }
]
}
}
},
methods: {
submitForm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!')
} else {
console.log('error submit!!')
return false
}
})
},
resetForm (formName) {
this.ruleForm = { ...this.$options.data().ruleForm }
this.$refs[formName].resetFields()
}
}
}
</script>

BAT命令行过长处理

背景

DBApi最新版并没有提供bat启动脚本,我在添加启动脚本时发现启动时容易出现命令行过长错误,本文就记录了该问题的处理

处理方式

Jar Manifest

该方式是把启动参数写入MANIFEST.INF中,并打包在一个jar包内,然后使用java -jar xxx.jar启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
@echo off

@REM ---------------------------------------------------------------------------
@REM Start script for the DBApi
@REM usage: dbapi.bat [-rebuild:if libs change]
@REM ---------------------------------------------------------------------------

setlocal enabledelayedexpansion

@REM get CURRENT_DIR
set CURRENT_DIR=%~dp0

cd "%CURRENT_DIR%.."

@REM set param
set DBAPI_HOME=%cd%
set DBAPI_CONF_HOME=file:/%DBAPI_HOME%\conf\
set DBAPI_LIB_HOME=%DBAPI_HOME%\lib
set DBAPI_MAIN_CLASS=com.gitee.freakchicken.dbapi.DBApiStandalone
set DBAPI_TEMP_PATH=%temp%\dbapi
set DBAPI_MANIFEST_FILE_PARENT_PATH=%DBAPI_TEMP_PATH%\META-INF
set DBAPI_MANIFEST_FILE_PATH=%DBAPI_MANIFEST_FILE_PARENT_PATH%\MANIFEST.MF
set DBAPI_START_JAR_PATH=%DBAPI_TEMP_PATH%\DBApi-start.jar
set DBAPI_EXCLUDE_JAR_NAMES=spring-boot-starter-webflux;spring-webflux;spring-cloud-gateway-server;spring-cloud-starter-gateway
set DBAPI_RE_BUILD_START_JAR_FLAG=%1


if not exist %DBAPI_MANIFEST_FILE_PARENT_PATH% md %DBAPI_MANIFEST_FILE_PARENT_PATH%

@REM if exist %DBAPI_MANIFEST_FILE_PATH% (
@REM del /f /q %DBAPI_MANIFEST_FILE_PATH%
@REM )

if exist %DBAPI_START_JAR_PATH% (
if "%DBAPI_RE_BUILD_START_JAR_FLAG%"=="-rebuild" (
call:rebuild_start_jar
)
) else (
call:rebuild_start_jar
)

java -jar %DBAPI_START_JAR_PATH%

:rebuild_start_jar
SETLOCAL
@REM set "DBAPI_JAR_PATH=%DBAPI_CONF_HOME%"
echo Manifest-Version: 1.0 > %DBAPI_MANIFEST_FILE_PATH%
echo Created-By: DBApi >> %DBAPI_MANIFEST_FILE_PATH%
echo Main-Class: %DBAPI_MAIN_CLASS% >> %DBAPI_MANIFEST_FILE_PATH%
echo Class-Path: %DBAPI_CONF_HOME% >> %DBAPI_MANIFEST_FILE_PATH%

set "DBAPI_JAR_PATH=%DBAPI_CONF_HOME%"
for /R %DBAPI_LIB_HOME% %%f in (*.jar) do (
set file_path=%%f
echo handle !file_path!
if defined file_path (
set result=0
call:exclude_jar %%f result
if !result!==0 (
(echo file:/%%f ) >> %DBAPI_MANIFEST_FILE_PATH%
)
)
)

jar -cvfm %DBAPI_START_JAR_PATH% %DBAPI_MANIFEST_FILE_PATH%
ENDLOCAL
goto:eof

:exclude_jar
SETLOCAL
set result=0
set jar_path=%1
if defined jar_path (
for %%i in (%DBAPI_EXCLUDE_JAR_NAMES%) do (
set result=0
call:is_str_same_as !jar_path! %%i result
@REM echo jar_path=!jar_path! str=%%i result=%result%
if !result!==1 (
goto break
)
)
) else (
ENDLOCAL & goto:eof
)
:break
ENDLOCAL&set %~2=%result%
goto:eof


:is_str_same_as
SETLOCAL
set result=0
set str=%1
set regexp=.*%2.*
if defined str (
echo %str%|findstr /r /c:"%regexp%" >nul && (
set result=1
) || (
set result=0
)
@REM echo str=%str% regexp=%regexp% result=%result%
) else (
ENDLOCAL & goto:eof
)
ENDLOCAL&set %~3=%result%
goto:eof

classpath file

这种启动方式来自于IDEA的Shorten Command Line,不过在JDK 9及以上版本被废除

代码位置:com.intellij.rt.execution.CommandLineWrapper

主要就是通过System.setProperty预先设置一些变量,然后通过反射调用主启动类的main方法启动

@argfile 方式

Java 9+支持的启动方式,来源于IDEA

HarmonyOS手机蓝牙日志

环境

1
HarmonyOS version: 2.0.0

问题描述

已经开启了USB调试HCI 信息收集日志功能

开发者选项

在尝试获取HCI log时

使用adb pull /data/log/bt时提示

1
/data/log/bt/: 0 files pulled, 0 skipped.

通过adb pull /data/log/bt/btsnoop_hci.log获取时提示

1
adb: error: failed to stat remote object '/data/log/bt/btsnoop_hci.log': No such file or directory

但是/etc/bluetooth/bt_stack.conf文件内配置如下

1
2
# BtSnoop log output file
BtSnoopFileName=/data/log/bt/btsnoop_hci.log

也就是说日志文件的路径应该是没有问题的

尝试通过ls /data/log/bt/ 查看目录下文件时,提示Permission denied,不能查看到目录

也无法通过adb root获取权限,那么获取HCI的日志的呢?

解决方案

  1. 开发者选项中 -> 启用蓝牙HCI信息收集日志 是打开的

  2. 进入手机拨号界面输入:*#*#2846579#*#*
    依次选择后台设置AP LOG设置点击打开 保存即可

    step1
    step2
    step3

  3. 重新操作蓝牙即可观察到日志,但是注意日志文件名并不是btsnoop_hci.log,而是附带了日期例如btsnoop_hci_20220716_101108.log

参考

  1. 请问HarmonyOS中如何获取HCI日志
  2. 华为手机抓取hci log

Mybatis MetaObject分析

MyBatis中的MetaObjectMyBatis中的反射工具,可以使用MetaObject完成获取值和设值的任务

本文基于MyBatis 3.5.7

作用

根据路径获取值和设值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
User user = buildUser();

MetaObject metaObject = SystemMetaObject.forObject(user);

// 查找user对象中第一个tag的创建者姓名
Object value = metaObject.getValue("tags[0].creator.name");
// Creator-0
System.out.println(value);

metaObject.setValue("tags[0].creator.name","CreatorName");

value = metaObject.getValue("tags[0].creator.name");
// CreatorName
System.out.println(value);

结构

meta_object_struct

这里面最主要的就是ObjectWrapper,而ObjectWrapper主要分为以下几类

  • Bean Wrapper
  • Map Wrapper
  • Collection Wrapper
  • 自定义 Wrapper

Bean Wrapper

BeanWrapperMetaClassMyBatis类反射工具类,使用它可以非常方便的使用java反射而无须担心访问权限的问题.

BeanWrapper本质上就是根据字符串递归利用反射使用get,set来获取值和设置值.

Map Wrapper

逻辑与BeanWrapper基本一致

Collection Wrapper

这个类非常简单,因为它的绝大多数操作都会直接抛出异常

可是当我们使用tags[0]时是可以取值的,这是为什么?

因为在我们使用tags[0]时走的是MapWrapperBeanWrapper共同父类BaseWrapper的逻辑而不是ColleactionWrapper

自定义 Wrapper

  • 确定自定义类
1
2
3
4
5
6
7
class CustomObject{
public String name;

public CustomObject(String name){
this.name = name;
}
}
  • 定义CustomObjectWrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 这里代码很多但是最主要的就是一个get,一个set
class CustomObjectWrapper implements ObjectWrapper{

private CustomObject object;

public CustomObjectWrapper(CustomObject object){
this.object = object;
}

@Override
public Object get(PropertyTokenizer prop) {
System.out.println("get");

if(prop.getName().equals("name")){
return this.object.name;
}
return null;
}

@Override
public void set(PropertyTokenizer prop, Object value) {
System.out.println("set");
if(prop.getName().equals("name")){
this.object.name = (String) value;
}
}

@Override
public String findProperty(String name, boolean useCamelCaseMapping) {
System.out.println("findProperty");
return null;
}

@Override
public String[] getGetterNames() {
System.out.println("getGetterNames");
return new String[0];
}

@Override
public String[] getSetterNames() {
System.out.println("getSetterNames");
return new String[0];
}

@Override
public Class<?> getSetterType(String name) {
System.out.println("getSetterType");
return null;
}

@Override
public Class<?> getGetterType(String name) {
System.out.println("getGetterType");
return null;
}

@Override
public boolean hasSetter(String name) {
System.out.println("hasSetter");
return false;
}

@Override
public boolean hasGetter(String name) {
System.out.println("hasGetter");
return false;
}

@Override
public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) {
System.out.println("instantiatePropertyValue");
return null;
}

@Override
public boolean isCollection() {
System.out.println("isCollection");
return false;
}

@Override
public void add(Object element) {
System.out.println("element");
}

@Override
public <E> void addAll(List<E> element) {
System.out.println("addAll");
}
}
  • 定义CustomObjectWrapperFactory
1
2
3
4
5
6
7
8
9
10
11
12
class CustomObjectWrapperFactory implements ObjectWrapperFactory{

@Override
public boolean hasWrapperFor(Object object) {
return object instanceof CustomObject;
}

@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return new CustomObjectWrapper((CustomObject)object);
}
}
  • 测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test() {
CustomObject customObject = new CustomObject("名字");
CustomObjectWrapperFactory customObjectWrapperFactory = new CustomObjectWrapperFactory();

// ObjectFactory和DefaultReflectorFactory使用默认的
ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

MetaObject metaObject = MetaObject.forObject(customObject,DEFAULT_OBJECT_FACTORY,customObjectWrapperFactory,defaultReflectorFactory);
Object name = metaObject.getValue("name");
System.out.println(name);

metaObject.setValue("name","名称1");
name = metaObject.getValue("name");
System.out.println(name);
}

dbapi是如何执行动态SQL的

dbapi可以解析类似MyBatis形式的SQL语句

整个执行过程分为两部分:

  • 1.解析SQL字符串
  • 2.执行SQL

本文将着重介绍它是如何执行SQL的

对标db-api版本:dfc0dac12d67e892e3ceb0b89dad773b2ab14642

解析

db-api使用orange解析字符串,将形如

1
2
3
4
5
6
7
select * from user
<where>
name in
<foreach collection='list' open='(' separator=',' close=')'>
#{item.name}
</foreach>
</where>

的SQL语句解析为SqlMeta对象,其中SqlMeta.sql属性为解析后的SQL字符串,SqlMeta.jdbcParamValues属性为解析后的参数列表

执行

通过分析找到真正执行的类com.gitee.freakchicken.dbapi.basic.servlet.APIServlet,这个类是一个HttpServlet

无论是get请求还是post请求,都会调用com.gitee.freakchicken.dbapi.basic.servlet.APIServlet#process方法

process方法分为以下几步:

  • 1.根据path获取API定义
  • 2.获取相关数据库配置
  • 3.从请求中获取参数
  • 4.缓存
  • 5.获取链接
  • 6.执行sql
  • 7.执行数据转换

以上各步,一旦出现异常就会发送提醒

这里最主要的就是第六步

executeSql执行sql

com.gitee.freakchicken.dbapi.basic.servlet.APIServlet#executeSql无非是根据配置设置连接是否自动提交事务,如果不是自动提交的事务,则按照执行结果提交或返回事务

这里最主要的是调用com.gitee.freakchicken.dbapi.basic.util.JdbcUtil#executeSql

而在JdbcUtil#executeSql里主要使用PreparedStatement执行sql

MyBatis SQL XML解析

说明

本文仅关注MyBatis SQL XML解析(select/update/insert/delete),不关注MyBatis的其他功能,如果想了解MyBatis的其他功能,可以参考MyBatis官网

解析过程

MyBatis_Mapper_sql_Xml解析

drawio文件下载

环境准备

  • 1.克隆MyBatis源码
1
git clone https://github.com/fuzi1996/mybatis-3.git

使用3.5.7_code_read分支

测试代码介绍

测试代码位置org.apache.ibatis.builder.XmlMapperBuilderTest#selfTest该单元测试主要测试mapper中的selectupdateinsertdelete语句的解析
对应xml文件地址:src\test\java\org\apache\ibatis\builder\SelfTestMapper.xml,可以看到这里面只有一个select语句

解析过程

1.构建XMLMapperBuilder

构建XMLMapperBuilder需要构建MyBatis的全局配置对象Configuration,这里直接new一个Configuration即可

1
2
3
4
5
6
Configuration configuration = new Configuration();
String resource = "org/apache/ibatis/builder/SelfTestMapper.xml";
// configuration.getSqlFragments()是mybatis中已缓存的sql片段
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
}

2.XMLMapperBuilder解析(parse)

1
2
3
                                            --> XMLIncludeTransformer
XMLMapperBuilder --> XMLStatementBuilder --|
--> LanguageDriver --> XMLScriptBuilder

经过该调用链将xml字符串转为SqlNode对象,并缓存到Configuration

OnlyOffice的安装

说明

OnlyOffice的安装分为使用OnlyOffice官方安装包和自行从源码编译安装两种

安装

使用官方二进制包安装

使用官方二进制包安装一切都使用默认配置,例如postgresqlrabbitmq的连接地址等

文档展示的是在Ubuntu上安装OnlyOffice

官方文档

  • 1.系统要求
1
2
3
4
5
6
7
8
9
10
CPU dual core 2 GHz or better
RAM 2 GB or more
HDD at least 40 GB of free space
Additional requirements at least 4 GB of swap
OS 64-bit Debian, Ubuntu or other compatible distribution with kernel version 3.13 or later
Additional requirements
PostgreSQL: version 12.9 or later
NGINX: version 1.3.13 or later
libstdc++6: version 4.8.4 or later
RabbitMQ
  • 2.安装依赖
1
2
3
sudo apt-get install postgresql
sudo apt-get install rabbitmq-server
sudo apt-get install nginx-extras
  • 3.数据库初始化
1
2
3
sudo -i -u postgres psql -c "CREATE DATABASE onlyoffice;"
sudo -i -u postgres psql -c "CREATE USER onlyoffice WITH password 'onlyoffice';"
sudo -i -u postgres psql -c "GRANT ALL privileges ON DATABASE onlyoffice TO onlyoffice;"

初始化数据库,数据库初始化文件在/server/schema/postgresql/createdb.sql

  • 4.安装OnlyOffice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Add GPG key:
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
gpg --no-default-keyring --keyring gnupg-ring:/tmp/onlyoffice.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys CB2DE8E5
chmod 644 /tmp/onlyoffice.gpg
sudo chown root:root /tmp/onlyoffice.gpg
sudo mv /tmp/onlyoffice.gpg /etc/apt/trusted.gpg.d/

# Add ONLYOFFICE Docs repository:
echo "deb https://download.onlyoffice.com/repo/debian squeeze main" | sudo tee /etc/apt/sources.list.d/onlyoffice.list

# install
sudo apt-get update
sudo apt-get install ttf-mscorefonts-installer
# 在安装onlyoffice-documentserver时会要求输入数据库密码
sudo apt-get install onlyoffice-documentserver

由于这里都在使用默认的配置,所以安装后OnlyOffice应该就在后台正常运行,访问本机80端口就可以看到OnlyOffice

  • 5.管理

OnlyOffice使用supervisorctl作为进程管理

1
2
# 查看supervisorctl管理的进程状态,以ds:开头的都是OnlyOffice的后台进程
sudo supervisorctl status

可以通过supervisorctl来开启或关闭OnlyOffice的后台进程

安装后,OnlyOffice就会自动在后台运行

自行编译安装

关于编译的部分,在从源码编译OnlyOffice这里不再赘述

OnlyOffice编译后,会在/build_tools/out下看到最终编译产物

以下是启动步骤:

  • 1.安装好必要的软件

  • 2.安装依赖

    具体配置省略,与使用官方二进制包安装一致

    • postgresql
      • 初始数据库
    • rabbitmq-server
  • 3.启动

    • 启动FileConverter

    参考脚本:

    1
    2
    3
    4
    5
    #!/bin/bash
    cd /build_tools/out/linux_64/onlyoffice/documentserver/server/FileConverter
    # NODE_ENV指定当前环境为development-linux,这样就会读取`development-linux.json`作为`default.json`的补充
    # NODE_CONFIG指定运行时的一些具体的配置数据,比如这里就指定了rabbitmq与postgresql的数据,需要自行修改
    LD_LIBRARY_PATH=$PWD/bin NODE_ENV=development-linux NODE_CONFIG_DIR=$PWD/../Common/config NODE_CONFIG='{"rabbitmq": {"url": "amqp://guest:guest@192.168.0.2:5672"},"services": {"CoAuthoring": {"sql": {"type": "postgres","dbHost": "192.168.0.2","dbPort": 5432,"dbName": "onlyoffice","dbUser": "onlyoffice","dbPass": "onlyoffice"},"redis": {"host": "192.168.0.2"}}}}' nohup ./converter > nohup.out 2>&1 &
    • 启动DocService
    1
    2
    3
    4
    5
    #!/bin/bash
    cd /build_tools/out/linux_64/onlyoffice/documentserver/server/DocService
    # NODE_ENV指定当前环境为development-linux,这样就会读取`development-linux.json`作为`default.json`的补充
    # NODE_CONFIG指定运行时的一些具体的配置数据,比如这里就指定了rabbitmq与postgresql的数据,需要自行修改
    LD_LIBRARY_PATH=$PWD/bin NODE_ENV=development-linux NODE_CONFIG_DIR=$PWD/../Common/config NODE_CONFIG='{"rabbitmq": {"url": "amqp://guest:guest@192.168.0.2:5672"},"services": {"CoAuthoring": {"sql": {"type": "postgres","dbHost": "192.168.0.2","dbPort": 5432,"dbName": "onlyoffice","dbUser": "onlyoffice","dbPass": "onlyoffice"},"redis": {"host": "192.168.0.2"}}}}' nohup ./docservice > nohup.out 2>&1 &
    • 启动示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/bash
    cd /build_tools/out/linux_64/onlyoffice/documentserver-example

    # 如果files文件夹不存在,就创建一个
    if [ ! -d "files" ]; then
    mkdir files
    fi

    # 指定DocService的地址,这里需要修改ip地址
    NODE_CONFIG='{"server":{"siteUrl":"http://192.168.234.128:8000/"}}' nohup ./example > nohup.out 2>&1 &

以上启动完成后,访问http://你的ip:3000就可以看到示例页面了

从源码编译OnlyOffice

环境要求

1
2
系统: Ubuntu 14.04
网络: 必须科学上网

为什么要求科学上网?
OnlyOffice源码存放在github上,且编译时会从google服务器下载v8相关资源,所以必须科学上网

说明

  • 锚定build_toolsgit版本:be06b3c2c8df1559d2198d3c7e186a9d5913799f

  • /build_tools路径指的是OnlyOffice编译工具仓库clone后的本地地址

  • OnlyOffice连接数限制在build_tools同级目录/server/Common/sources/contants.js

    1
    2
    // 改为9999就可以无限连接数
    exports.LICENSE_CONNECTIONS = 20;

Linux下源码编译步骤

  • 1.build_tools工具下载
1
git clone https://github.com/ONLYOFFICE/build_tools.git
  • 2.相关资源预下载

    • node环境准备

      这里不再介绍如何安装node,但有以下几点需要注意:

      • node版本必须大于10.20

      • 如果想使用国内的npm镜像,例如淘宝的npm镜像,我在尝试的时候发现一些包不能正确安装,因此最好是科学上网,不使用镜像

      • 每一次编译的时候,build_tools都会尝试全局安装grunt-clipkg这两个包,因此可以提前安装好,然后修改build_tools源码,避免每次都安装

        • npm安装grunt-clipkg
        1
        2
        npm install -g grunt-cli
        npm install -g pkg
        • 修改\build_tools\tools\linux\deps.py中源码,注释掉以下两行
        1
        2
        # base.cmd("sudo", ["npm", "install", "-g", "grunt-cli"])
        # base.cmd("sudo", ["npm", "install", "-g", "pkg"])
    • java环境准备

      这里不再介绍具体的java安装,但须注意以下几点:

      • java版本仅支持11,1.8

      • 编译的时候每次都会尝试全局安装open-jdk,如果java环境已经配置好,可以修改\build_tools\tools\linux\deps.py中源码,注释掉以下几行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # java_error = base.cmd("sudo", ["apt-get", "-y", "install", "openjdk-11-jdk"], True)
      # if (0 != java_error):
      # java_error = base.cmd("sudo", ["apt-get", "-y", "install", "openjdk-8-jdk"], True)
      # if (0 != java_error):
      # base.cmd("sudo", ["apt-get", "-y", "install", "software-properties-common"])
      # base.cmd("sudo", ["add-apt-repository", "-y", "ppa:openjdk-r/ppa"])
      # base.cmd("sudo", ["apt-get", "update"])
      # base.cmd("sudo", ["apt-get", "-y", "install", "openjdk-8-jdk"])
      # base.cmd("sudo", ["update-alternatives", "--config", "java"])
      # base.cmd("sudo", ["update-alternatives", "--config", "javac"])
    • qt环境准备

      qt版本:5.9.9

      具体下载地址:https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.9/5.9.9/single/qt-everywhere-opensource-src-5.9.9.tar.xz

      • 重命名
        下载后将文件名改为qt_source_5.9.9.tar.xz并放置在/build_tools/tools/linux文件夹下

      • 解压
        解压/build_tools/tools/linux/qt_source_5.9.9.tar.xz/build_tools/tools/linux/qt-everywhere-opensource-src-5.9.9文件加下

    为什么需要资源预下载
    如果不预下载一些资源,会在编译时自动下载.但是编译时下载的资源很慢,碰上网络波动,极易造成编译失败

  • 3.编译

进入/build_tools/tools/linux目录下

1
./automate.py server

编译完成后,可以在/build_tools/out下看到最终编译产物

初始编译通过后,如果不需要更新OnlyOffice的代码,需要修改/build_tools/tools/linux/automate.py

1
2
3
4
5
build_tools_params = ["--branch", branch, 
"--module", modules,
# 这里需要把1改为0,这样后面就不会更新仓库
"--update", "0",
"--qt-dir", os.getcwd() + "/qt_build/Qt-5.9.9"] + params
  • 4.编译字体与主题

第3步,编译产物/build_tools/out下没有字体与主题资源,所以还需要生成字体与主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
cd /build_tools/out/linux_64/onlyoffice/documentserver

# 如果fonts文件夹不存在
if [ ! -d "fonts" ]; then
mkdir fonts
fi
# 在/build_tools/out/linux_64/onlyoffice/documentserver/fonts下生成字体
LD_LIBRARY_PATH=${PWD}/server/FileConverter/bin server/tools/allfontsgen \
--input="${PWD}/core-fonts" \
--allfonts-web="${PWD}/sdkjs/common/AllFonts.js" \
--allfonts="${PWD}/server/FileConverter/bin/AllFonts.js" \
--images="${PWD}/sdkjs/common/Images" \
--selection="${PWD}/server/FileConverter/bin/font_selection.bin" \
--output-web='fonts' \
--use-system="true"

# 生成主题
cd /build_tools/out/linux_64/onlyoffice/documentserver
LD_LIBRARY_PATH=${PWD}/server/FileConverter/bin server/tools/allthemesgen \
--converter-dir="${PWD}/server/FileConverter/bin"\
--src="${PWD}/sdkjs/slide/themes"\
--output="${PWD}/sdkjs/common/Images"