多维表格简介
以下是近半年仍在维护的具有多维表格特征的开源项目,为了清晰地了解它们的特性和未来开发方向,当然是把slogan列出来。可以看出,这些项目大多以“Airtable alternative”和“No-Code”自居。但不管他们的口号如何,我们还是以一个“多维表格”的视角来作评估选择。
- NocoDB ,Open source Airtable alternative.
- Baserow ,An open source no-code database and Airtable alternative. Create your own database without technical experience. Our user friendly no-code tool gives you the powers of a developer without leaving your browser.
- Teable ,AI No-code Database, Productivity Super Boost.
- APITable , An API-oriented low-code platform for building collaborative apps and better than all other Airtable open-source alternatives.
- Rowy ,Low-code backend platform. Manage database on spreadsheet-like UI and build cloud functions workflows in JS/TS, all in your browser.
- undb ,Private first self-hosted no code database & BaaS.
除了上述几款产品外,在开源社区我们还能看到以下几款具有“表格”形态的项目,Grist更像Excel表格,支持Excel的公式;Mathesar偏向于Postgres数据库的可视化工具;NocoBase则更多地服务于低代码应用构建。
- Grist ,A modern, open source spreadsheet that goes beyond the grid.
- Mathesar ,Secure, spreadsheet-like tool for Postgres data.
- NocoBase ,Extensibility-first open-source no-code platform.
综合来看,NocoDB,Baserow,Teable 和 APITable 是最像多维表格、功能相对完善、Bug相对较少的的几款产品。本文只关注这四个产品。( Rowy 依赖 Firebase,无法做到完全的自部署,undb 目前有不少基础功能的缺失,使用时也有不少 bug,暂不做推荐。)
多维表格特性说明
一些关于多维表格的基本特性说明。在一个多维表格里,用户端的交互层级由上至下是“Workspace/Space/空间/工作区”->“Database/数据库”→“Grid/表格”→“Views视图”,表格内表头一般称为“Field/字段”。一张表格可以演化为多个视图,表格间可以通过“链接”、“Lookup”、“Rollup”相互引用数据。后端可以通过API访问用户的所有数据。软件提供商一般也会围绕这些核心功能做一些用量限制以刺激增值服务。

baserow 部署
创建目录
代码块 |
---|
|
mkdir -pv /data/baserow/{caddy_config,caddy_data,media,pgdata} |
.env
仅列出初步运行所需更改的部分
代码块 |
---|
|
SECRET_KEY=lf6ra1wsz4e5ljw0dylsltryws2ioer6g1neffhfuuewynvsee
DATABASE_PASSWORD=27hd9rvxebg1b9m0lxfv8r7qjq7zdytaillg6n2omvvrnk8t1j
REDIS_PASSWORD=e3b0da8qcvpg5b8z35zptc19vpsatd2dlivme0ya8vsqwjeaf5`
# To increase the security of the SECRET_KEY, you should also set
# BASEROW_JWT_SIGNING_KEY=
# The browser URL you will access Baserow with. Used to connect to the api, generate emails, etc.
BASEROW_PUBLIC_URL=http://192.168.182.100
...... |
baserow.yml
代码块 |
---|
|
version: "3.4"
# See https://baserow.io/docs/installation%2Fconfiguration for more details on these
# backend environment variables, their defaults if left blank etc.
x-backend-variables: &backend-variables
# Most users should only need to set these first four variables.
SECRET_KEY: ${SECRET_KEY:?}
BASEROW_JWT_SIGNING_KEY: ${BASEROW_JWT_SIGNING_KEY:-}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:?}
REDIS_PASSWORD: ${REDIS_PASSWORD:?}
# If you manually change this line make sure you also change the duplicate line in
# the web-frontend service.
BASEROW_PUBLIC_URL: ${BASEROW_PUBLIC_URL-http://localhost}
# Set these if you want to use an external postgres instead of the db service below.
DATABASE_USER: ${DATABASE_USER:-baserow}
DATABASE_NAME: ${DATABASE_NAME:-baserow}
DATABASE_HOST:
DATABASE_PORT:
DATABASE_OPTIONS:
DATABASE_URL:
# Set these if you want to use an external redis instead of the redis service below.
REDIS_HOST:
REDIS_PORT:
REDIS_PROTOCOL:
REDIS_URL:
REDIS_USER:
# Set these to enable Baserow to send emails.
EMAIL_SMTP:
EMAIL_SMTP_HOST:
EMAIL_SMTP_PORT:
EMAIL_SMTP_USE_TLS:
EMAIL_SMTP_USE_SSL:
EMAIL_SMTP_USER:
EMAIL_SMTP_PASSWORD:
EMAIL_SMTP_SSL_CERTFILE_PATH:
EMAIL_SMTP_SSL_KEYFILE_PATH:
FROM_EMAIL:
# Set these to use AWS S3 bucket to store user files.
AWS_ACCESS_KEY_ID:
AWS_SECRET_ACCESS_KEY:
AWS_STORAGE_BUCKET_NAME:
AWS_S3_REGION_NAME:
AWS_S3_ENDPOINT_URL:
AWS_S3_CUSTOM_DOMAIN:
# Misc settings see https://baserow.io/docs/installation%2Fconfiguration for info
BASEROW_AMOUNT_OF_WORKERS:
BASEROW_ROW_PAGE_SIZE_LIMIT:
BATCH_ROWS_SIZE_LIMIT:
INITIAL_TABLE_DATA_LIMIT:
BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB:
BASEROW_OPENAI_UPLOADED_FILE_SIZE_LIMIT_MB:
BASEROW_UNIQUE_ROW_VALUES_SIZE_LIMIT:
BASEROW_EXTRA_ALLOWED_HOSTS:
ADDITIONAL_APPS:
BASEROW_PLUGIN_GIT_REPOS:
BASEROW_PLUGIN_URLS:
BASEROW_ENABLE_SECURE_PROXY_SSL_HEADER:
MIGRATE_ON_STARTUP: ${MIGRATE_ON_STARTUP:-true}
SYNC_TEMPLATES_ON_STARTUP: ${SYNC_TEMPLATES_ON_STARTUP:-true}
BASEROW_SYNC_TEMPLATES_PATTERN:
DONT_UPDATE_FORMULAS_AFTER_MIGRATION:
BASEROW_TRIGGER_SYNC_TEMPLATES_AFTER_MIGRATION:
BASEROW_SYNC_TEMPLATES_TIME_LIMIT:
BASEROW_BACKEND_DEBUG:
BASEROW_BACKEND_LOG_LEVEL:
FEATURE_FLAGS:
BASEROW_ENABLE_OTEL:
BASEROW_DEPLOYMENT_ENV:
OTEL_EXPORTER_OTLP_ENDPOINT:
OTEL_RESOURCE_ATTRIBUTES:
POSTHOG_PROJECT_API_KEY:
POSTHOG_HOST:
PRIVATE_BACKEND_URL: http://backend:8000
PUBLIC_BACKEND_URL:
PUBLIC_WEB_FRONTEND_URL:
BASEROW_EMBEDDED_SHARE_URL:
MEDIA_URL:
MEDIA_ROOT:
BASEROW_AIRTABLE_IMPORT_SOFT_TIME_LIMIT:
HOURS_UNTIL_TRASH_PERMANENTLY_DELETED:
OLD_ACTION_CLEANUP_INTERVAL_MINUTES:
MINUTES_UNTIL_ACTION_CLEANED_UP:
BASEROW_GROUP_STORAGE_USAGE_QUEUE:
DISABLE_ANONYMOUS_PUBLIC_VIEW_WS_CONNECTIONS:
BASEROW_WAIT_INSTEAD_OF_409_CONFLICT_ERROR:
BASEROW_DISABLE_MODEL_CACHE:
BASEROW_PLUGIN_DIR:
BASEROW_JOB_EXPIRATION_TIME_LIMIT:
BASEROW_JOB_CLEANUP_INTERVAL_MINUTES:
BASEROW_ROW_HISTORY_CLEANUP_INTERVAL_MINUTES:
BASEROW_ROW_HISTORY_RETENTION_DAYS:
BASEROW_USER_LOG_ENTRY_CLEANUP_INTERVAL_MINUTES:
BASEROW_USER_LOG_ENTRY_RETENTION_DAYS:
BASEROW_IMPORT_EXPORT_RESOURCE_CLEANUP_INTERVAL_MINUTES:
BASEROW_IMPORT_EXPORT_RESOURCE_REMOVAL_AFTER_DAYS:
BASEROW_IMPORT_EXPORT_TABLE_ROWS_COUNT_LIMIT:
BASEROW_MAX_ROW_REPORT_ERROR_COUNT:
BASEROW_JOB_SOFT_TIME_LIMIT:
BASEROW_FRONTEND_JOBS_POLLING_TIMEOUT_MS:
BASEROW_INITIAL_CREATE_SYNC_TABLE_DATA_LIMIT:
BASEROW_MAX_SNAPSHOTS_PER_GROUP:
BASEROW_SNAPSHOT_EXPIRATION_TIME_DAYS:
BASEROW_WEBHOOKS_ALLOW_PRIVATE_ADDRESS:
BASEROW_WEBHOOKS_IP_BLACKLIST:
BASEROW_WEBHOOKS_IP_WHITELIST:
BASEROW_WEBHOOKS_URL_REGEX_BLACKLIST:
BASEROW_WEBHOOKS_URL_CHECK_TIMEOUT_SECS:
BASEROW_WEBHOOKS_MAX_CONSECUTIVE_TRIGGER_FAILURES:
BASEROW_WEBHOOKS_MAX_RETRIES_PER_CALL:
BASEROW_WEBHOOKS_MAX_PER_TABLE:
BASEROW_WEBHOOKS_MAX_CALL_LOG_ENTRIES:
BASEROW_WEBHOOKS_REQUEST_TIMEOUT_SECONDS:
BASEROW_ENTERPRISE_AUDIT_LOG_CLEANUP_INTERVAL_MINUTES:
BASEROW_ENTERPRISE_AUDIT_LOG_RETENTION_DAYS:
BASEROW_ALLOW_MULTIPLE_SSO_PROVIDERS_FOR_SAME_ACCOUNT:
BASEROW_STORAGE_USAGE_JOB_CRONTAB:
BASEROW_SEAT_USAGE_JOB_CRONTAB:
BASEROW_PERIODIC_FIELD_UPDATE_CRONTAB:
BASEROW_PERIODIC_FIELD_UPDATE_UNUSED_WORKSPACE_INTERVAL_MIN:
BASEROW_PERIODIC_FIELD_UPDATE_TIMEOUT_MINUTES:
BASEROW_PERIODIC_FIELD_UPDATE_QUEUE_NAME:
BASEROW_MAX_CONCURRENT_USER_REQUESTS:
BASEROW_CONCURRENT_USER_REQUESTS_THROTTLE_TIMEOUT:
BASEROW_SEND_VERIFY_EMAIL_RATE_LIMIT:
BASEROW_LOGIN_ACTION_LOG_LIMIT:
BASEROW_OSS_ONLY:
OTEL_TRACES_SAMPLER:
OTEL_TRACES_SAMPLER_ARG:
OTEL_PER_MODULE_SAMPLER_OVERRIDES:
BASEROW_CACHALOT_ENABLED:
BASEROW_CACHALOT_MODE:
BASEROW_CACHALOT_ONLY_CACHABLE_TABLES:
BASEROW_CACHALOT_UNCACHABLE_TABLES:
BASEROW_CACHALOT_TIMEOUT:
BASEROW_BUILDER_PUBLICLY_USED_PROPERTIES_CACHE_TTL_SECONDS:
BASEROW_BUILDER_DISPATCH_ACTION_CACHE_TTL_SECONDS:
BASEROW_AUTO_INDEX_VIEW_ENABLED:
BASEROW_PERSONAL_VIEW_LOWEST_ROLE_ALLOWED:
BASEROW_DISABLE_LOCKED_MIGRATIONS:
BASEROW_USE_PG_FULLTEXT_SEARCH:
BASEROW_AUTO_VACUUM:
BASEROW_BUILDER_DOMAINS:
SENTRY_DSN:
SENTRY_BACKEND_DSN:
BASEROW_OPENAI_API_KEY:
BASEROW_OPENAI_ORGANIZATION:
BASEROW_OPENAI_MODELS:
BASEROW_OPENROUTER_API_KEY:
BASEROW_OPENROUTER_ORGANIZATION:
BASEROW_OPENROUTER_MODELS:
BASEROW_ANTHROPIC_API_KEY:
BASEROW_ANTHROPIC_MODELS:
BASEROW_MISTRAL_API_KEY:
BASEROW_MISTRAL_MODELS:
BASEROW_OLLAMA_HOST:
BASEROW_OLLAMA_MODELS:
BASEROW_SERVE_FILES_THROUGH_BACKEND:
BASEROW_SERVE_FILES_THROUGH_BACKEND_PERMISSION:
BASEROW_SERVE_FILES_THROUGH_BACKEND_EXPIRE_SECONDS:
BASEROW_ICAL_VIEW_MAX_EVENTS: ${BASEROW_ICAL_VIEW_MAX_EVENTS:-}
BASEROW_ACCESS_TOKEN_LIFETIME_MINUTES:
BASEROW_REFRESH_TOKEN_LIFETIME_HOURS:
BASEROW_PREVENT_POSTGRESQL_DATA_SYNC_CONNECTION_TO_DATABASE:
BASEROW_POSTGRESQL_DATA_SYNC_BLACKLIST:
BASEROW_ASGI_HTTP_MAX_CONCURRENCY: ${BASEROW_ASGI_HTTP_MAX_CONCURRENCY:-}
BASEROW_MAX_WEBHOOK_CALLS_IN_QUEUE_PER_WEBHOOK:
BASEROW_MAX_HEALTHY_CELERY_QUEUE_SIZE:
BASEROW_ENTERPRISE_PERIODIC_DATA_SYNC_CHECK_INTERVAL_MINUTES:
BASEROW_ENTERPRISE_MAX_PERIODIC_DATA_SYNC_CONSECUTIVE_ERRORS:
BASEROW_USE_LOCAL_CACHE:
BASEROW_WEBHOOKS_BATCH_LIMIT:
BASEROW_WEBHOOK_ROWS_ENTER_VIEW_BATCH_SIZE:
BASEROW_DEADLOCK_INITIAL_BACKOFF:
BASEROW_DEADLOCK_MAX_RETRIES:
BASEROW_PREMIUM_GROUPED_AGGREGATE_SERVICE_MAX_SERIES:
BASEROW_PREMIUM_GROUPED_AGGREGATE_SERVICE_MAX_AGG_BUCKETS:
services:
# A caddy reverse proxy sitting in-front of all the services. Responsible for routing
# requests to either the backend or web-frontend and also serving user uploaded files
# from the media volume.
caddy:
image: caddy:2
restart: unless-stopped
environment:
# Controls what port the Caddy server binds to inside its container.
BASEROW_CADDY_ADDRESSES: ${BASEROW_CADDY_ADDRESSES:-:80}
PRIVATE_WEB_FRONTEND_URL: ${PRIVATE_WEB_FRONTEND_URL:-http://web-frontend:3000}
PRIVATE_BACKEND_URL: ${PRIVATE_BACKEND_URL:-http://backend:8000}
BASEROW_PUBLIC_URL: ${BASEROW_PUBLIC_URL:-}
ports:
- "${HOST_PUBLISH_IP:-0.0.0.0}:${WEB_FRONTEND_PORT:-80}:80"
- "${HOST_PUBLISH_IP:-0.0.0.0}:${WEB_FRONTEND_SSL_PORT:-443}:443"
volumes:
- $PWD/Caddyfile:/etc/caddy/Caddyfile
- media:/baserow/media
- caddy_config:/config
- caddy_data:/data
networks:
local:
backend:
image: baserow/backend:1.33.4
restart: unless-stopped
environment:
<<: *backend-variables
depends_on:
- db
- redis
volumes:
- media:/baserow/media
networks:
local:
web-frontend:
image: baserow/web-frontend:1.33.4
restart: unless-stopped
environment:
BASEROW_PUBLIC_URL: ${BASEROW_PUBLIC_URL-http://localhost}
PRIVATE_BACKEND_URL: ${PRIVATE_BACKEND_URL:-http://backend:8000}
PUBLIC_BACKEND_URL:
PUBLIC_WEB_FRONTEND_URL:
BASEROW_EMBEDDED_SHARE_URL:
BASEROW_DISABLE_PUBLIC_URL_CHECK:
INITIAL_TABLE_DATA_LIMIT:
DOWNLOAD_FILE_VIA_XHR:
BASEROW_DISABLE_GOOGLE_DOCS_FILE_PREVIEW:
BASEROW_DISABLE_SUPPORT:
HOURS_UNTIL_TRASH_PERMANENTLY_DELETED:
DISABLE_ANONYMOUS_PUBLIC_VIEW_WS_CONNECTIONS:
FEATURE_FLAGS:
ADDITIONAL_MODULES:
BASEROW_MAX_IMPORT_FILE_SIZE_MB:
BASEROW_MAX_SNAPSHOTS_PER_GROUP:
BASEROW_ENABLE_OTEL:
BASEROW_DEPLOYMENT_ENV:
BASEROW_OSS_ONLY:
BASEROW_USE_PG_FULLTEXT_SEARCH:
POSTHOG_PROJECT_API_KEY:
POSTHOG_HOST:
BASEROW_UNIQUE_ROW_VALUES_SIZE_LIMIT:
BASEROW_ROW_PAGE_SIZE_LIMIT:
BASEROW_BUILDER_DOMAINS:
BASEROW_FRONTEND_SAME_SITE_COOKIE:
SENTRY_DSN:
BASEROW_PREMIUM_GROUPED_AGGREGATE_SERVICE_MAX_SERIES:
BASEROW_PREMIUM_GROUPED_AGGREGATE_SERVICE_MAX_AGG_BUCKETS:
depends_on:
- backend
networks:
local:
celery:
image: baserow/backend:1.33.4
restart: unless-stopped
environment:
<<: *backend-variables
command: celery-worker
# The backend image's baked in healthcheck defaults to the django healthcheck
# override it to the celery one here.
healthcheck:
test: [ "CMD-SHELL", "/baserow/backend/docker/docker-entrypoint.sh celery-worker-healthcheck" ]
depends_on:
- backend
volumes:
- media:/baserow/media
networks:
local:
celery-export-worker:
image: baserow/backend:1.33.4
restart: unless-stopped
command: celery-exportworker
environment:
<<: *backend-variables
# The backend image's baked in healthcheck defaults to the django healthcheck
# override it to the celery one here.
healthcheck:
test: [ "CMD-SHELL", "/baserow/backend/docker/docker-entrypoint.sh celery-exportworker-healthcheck" ]
depends_on:
- backend
volumes:
- media:/baserow/media
networks:
local:
celery-beat-worker:
image: baserow/backend:1.33.4
restart: unless-stopped
command: celery-beat
environment:
<<: *backend-variables
# See https://github.com/sibson/redbeat/issues/129#issuecomment-1057478237
stop_signal: SIGQUIT
depends_on:
- backend
volumes:
- media:/baserow/media
networks:
local:
db:
image: chaitin/safeline-postgres:15.2
# If you were using a previous version, perform the update by uncommenting the
# following line. See: https://baserow.io/docs/installation%2Finstall-with-docker#upgrading-postgresql-database-from-a-previous-version
# for more information.
# image: pgautoupgrade/pgautoupgrade:15-alpine3.8
restart: unless-stopped
environment:
- POSTGRES_USER=${DATABASE_USER:-baserow}
- POSTGRES_PASSWORD=${DATABASE_PASSWORD:?}
- POSTGRES_DB=${DATABASE_NAME:-baserow}
healthcheck:
test: [ "CMD-SHELL", "su postgres -c \"pg_isready -U ${DATABASE_USER:-baserow}\"" ]
interval: 10s
timeout: 5s
retries: 5
networks:
local:
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:6
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD:?}
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
networks:
local:
# By default, the media volume will be owned by root on startup. Ensure it is owned by
# the same user that django is running as, so it can write user files.
volume-permissions-fixer:
image: bash:4.4
command: chown 9999:9999 -R /baserow/media
volumes:
- media:/baserow/media
networks:
local:
volumes:
pgdata:
driver: local
driver_opts:
type: none
o: bind
device: /data/baserow/pgdata
media:
driver: local
driver_opts:
type: none
o: bind
device: /data/baserow/media
caddy_data:
driver: local
driver_opts:
type: none
o: bind
device: /data/baserow/caddy_data
caddy_config:
driver: local
driver_opts:
type: none
o: bind
device: /data/baserow/caddy_config
networks:
local:
driver: bridge |

绕过付费机制
Baserow 的付费特性有:
- 视图,调查表单、看板、日历、甘特图和视图锁定;
- 字段,Al prompt;
- 导入导出,导出成XML、JSON;
- 数据连接,Internal Base、iCal、Jira issues、GitLab issues、GitHub issues、HubSpot customers;
- 其它,Single sign-on (SSO)、Audit logs、SAML、Roles、Row comments、Row coloring、Charts、Al Chat。
Baserow许可验证机制
Baserow使用了非对称加密的数字许可证验证机制,其中,
License内容:
- 包含实例ID、许可证类型、有效时间、席位等关键授权信息;
- 采用Base64编码进行传输;
加密签名:
- 使用私钥(SHA256)对license进行数字签名;
- 客户端使用公钥验证签名真实性;
验证流程:
- 服务器生成并签名license;
- 客户端验证签名并解码license;
- 根据license内容解锁相应功能;
因此,我们要做的就是生成自己的公钥私钥对,自定义License内容并使用私钥签名,将远程认证服务器改成本地服务。
baserow.yml
代码块 |
---|
|
version: "3.4"
services:
baserow:
container_name: baserow
image: baserow/baserow:1.33.4
environment:
BASEROW_PUBLIC_URL: 'http://192.168.182.100:3001'
TZ: 'Asia/Shanghai'
ports:
- "3001:80"
- "443:443"
volumes:
- /data/baserow/data:/baserow/data
n8n:
image: n8nio/n8n:1.98.2
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_SECURE_COOKIE=false
- GENERIC_TIMEZONE=Asia/Shanghai
- TZ=Asia/Shanghai
- N8N_PROTOCOL=http
- NODE_ENV=production
volumes:
- /data/baserow/n8n_data:/home/node/.n8n |
操作流程
以docker容器部署的 Baserow 为例,容器名为 baserow:
1、更改宿主机 hosts,将认证服务器指向本地,避免升级时对官方服务器发起请求;
代码块 |
---|
|
# sudo vim /etc/hosts
127.0.0.1 api.baserow.io |
2、生成密钥对 python creat_pem.py;
代码块 |
---|
|
# creat_pem.py
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
print("=== PRIVATE KEY ===")
print(private_pem.decode())
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print("\n=== PUBLIC KEY ===")
print(public_pem.decode()) |
3、将公钥内容保存至 public_key.pem 文件并替换容器内的公钥;
代码块 |
---|
|
docker cp public_key.pem baserow:/baserow/premium/backend/src/baserow_premium/
docker exec -it -u root baserow bash
chmod a+rw /baserow/premium/backend/src/baserow_premium/public_key.pem
chown 9999:9999 /baserow/premium/backend/src/baserow_premium/public_key.pem |
4、修改容器内的认证服务器;
代码块 |
---|
|
docker cp baserow:/baserow/backend/src/baserow/core/utils.py ./ |
将 base_url 的值改为自建 http 服务器地址,如 https://api.example.com,可以由n8n、nodered 或 flask 等任意服务建立,如 n8n 服务器 http://{N8N_EDITOR_BASE_URL}:N8N_PORT}/{N8N_ENDPOINT_WEBHOOK}
代码块 |
---|
|
# vim utils.py
base_url = "https://api.baserow.io"
# 替换覆盖容器内的源码
docker cp utils.py baserow:/baserow/backend/src/baserow/core/ |
5、重启Baserow
代码块 |
---|
|
docker compose restart |
6、生成证书,其中证书格式如下
代码块 |
---|
|
# creat_lic.py
import base64
import hashlib
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key
data = '{"version": 1,"id": "1","valid_from": "2025-01-01T00:00:00","valid_through": "2035-12-31T23:59:59","product_code": "enterprise","seats": 5,"issued_on": "2025-01-01T00:00:00","issued_to_email": "peter@baserow.io","issued_to_name": "Peter","instance_id": "abcdefghijklmn-U"}'
private_key_pem = b"""
-----BEGIN PRIVATE KEY-----
MIIEvQI........
-----END PRIVATE KEY-----
"""
private_key = load_pem_private_key(private_key_pem, password=None)
payload_base64 = base64.urlsafe_b64encode(data.encode())
pre_hashed = hashlib.sha256(payload_base64).hexdigest().encode()
signature = private_key.sign(
pre_hashed,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256(),
)
signature_base64 = base64.urlsafe_b64encode(signature)
license_payload = payload_base64 + b"." + signature_base64
print("Generated license_payload:", license_payload.decode()) |
根据实际修改以上data证书内容,各项含义如下:
代码块 |
---|
|
{
"version": 1, //请勿修改
"id": "123-2-435-gfg-4tg54", //证书id,随意设置
"valid_from": "2025-02-01T00:00:00", // 随意设置开始日期
"valid_through": "2028-01-01T23:59:59", // 随意设置到期日期
"product_code": "enterprise", // premium,advanced,enterprise,参考baserow.io/pricing,一般就设置为最高的enterprise
"seats": 10, // 随意设置
"application_users": 10, // 随意设置
"issued_on": "2025-02-11T13:36:21.075873", // 随意设置
"issued_to_email": "peter@baserow.io", // 管理员邮箱, /admin/users中查看
"issued_to_name": "Peter", // 管理员姓名Name, /admin/users中查看
"instance_id": "abcdefghijklmn-U", //实例ID,/admin/licenses中查看'Your Baserow instance ID is'
} |
生成证书 python creat_lic.py
代码块 |
---|
|
python creat_lic.py
# 将打印如下
# Generated license_payload: eyJ2...... |
7、编写认证服务响应
代码块 |
---|
|
{
"eyJ2......":
{
"type": "ok",
"detail": ""
}
} |
可以用 n8 n的 Webhook+Respond to Webhook 节点配置响应。
在 Webhook 节点中 HTTP Method 设置为 POST,PATH 设置为 /api/saas/licenses/check/,Respond 设置为Using Respond to Webhook Node;
在 Respond to Webhook 节点中,Respond With 改为 JSON,Response Body 填入上述 json 内容,Response Code 设置为200



8、测试认证
在 Baserow 中 /admin/licenses,点击 Register license 按钮,在 License key 框中填入 eyJ2...... 证书值,点击确认完成;我们在 n8n 的执行历史中即可看到请求记录。
9、Baserow的升级,重复执行上述步骤3-5即可,也可以一劳永逸地在 docker-compose.yml 中映射上述两个文件至容器内;(注意utils.py可能随版本升级而有文件变动)

n8n 的 nginx 代理
代码块 |
---|
|
server {
listen 80;
server_name your_domian.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name your_domian.com;
client_max_body_size 20M;
#add_header alt-svc 'h3=":443"; ma=2592000';
# 证书的公私钥
ssl_certificate /etc/nginx/cert/cert.pem;
ssl_certificate_key /etc/nginx/cert/cert.key;
#add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
# gzip config
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_http_version 1.1; # 必要 否则会出现执行任务没有响应的情况
proxy_set_header Upgrade $http_upgrade;# 必要 否则会出现执行任务没有响应的情况
proxy_set_header Connection "upgrade";# 必要 否则会出现执行任务没有响应的情况
}
} |