Kubernetes 安装部署 Alist 并配置 Onlyoffice
一、简单介绍
Alist 是一款支持多种存储的目录列表程序,可以将你的网盘、对象存储、本地存储等挂载成一个统一的目录,方便浏览、管理和分享文件。简单来说,它就像一个强大的文件管理器,可以让你轻松访问各种存储设备上的文件,并提供在线预览、下载、分享等功能。

Alist 的优势:
支持多种存储: 支持 OneDrive、Google Drive、阿里云盘、百度网盘、WebDAV、S3 等多种存储方式。
界面简洁美观: 采用 Material Design 风格,界面简洁易用。
功能强大: 支持文件预览、下载、分享、搜索、权限管理等功能。
安全可靠: 数据存储在你的存储设备上,安全可靠。
开源免费: 开源免费,可以自由使用和修改。
部署方式:
Alist 支持多种部署方式,包括 Docker、宝塔面板、直接运行等。这里以 K8S 部署为例,介绍部署方法。
并且配合使用 onlyoffice 工具打开 Office 办公文档
并且本次部署所需要的域名均为 https,并且由于我的证书是自签名的证书,所以如下配置过程中还使用相关的配置来完成了本地信任证书,以便服务可以正常访问
二、开始部署
1. 部署 Alist 服务
目录结构
[root@k8s-master1 alist-server]# tree
.
├── deployment.yaml
├── onlyoffice
│ ├── Dockerfile
│ ├── onlyoffice-deployment.yaml
│ └── onlyoffice-nginx.yaml
└── pvc.yaml
1 directory, 5 files部署 alist-deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: alist
namespace: k8s-app
spec:
replicas: 1
selector:
matchLabels:
app: alist
template:
metadata:
labels:
app: alist
spec:
containers:
- name: alist
image: harbor.tianxiang.love:30443/k8s-app/alist:latest-aio
ports:
- containerPort: 5244
env:
- name: TZ
value: "Asia/Shanghai"
- name: PUID
value: "0"
- name: PGID
value: "0"
- name: UMASK
value: "022"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "2048Mi"
cpu: "500m"
volumeMounts:
- name: alist-data
mountPath: /opt/alist/data
livenessProbe:
httpGet:
path: /
port: 5244
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 5244
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: alist-data
persistentVolumeClaim:
claimName: alist-pvc
---
apiVersion: v1
kind: Service
metadata:
name: alist-service
namespace: k8s-app
labels:
app: alist
spec:
selector:
app: alist
ports:
- port: 80
targetPort: 5244
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: alist-ingress
namespace: k8s-app
annotations:
# 正则表达式来匹配路径
nginx.ingress.kubernetes.io/use-regex: "true"
# 设置为"0"表示没有限制请求体的大小
nginx.ingress.kubernetes.io/proxy-body-size: "0"
# 以下是新增的超时相关配置
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
# 对于大文件上传可能需要更长时间
nginx.ingress.kubernetes.io/proxy-next-upstream-timeout: "600"
labels:
app: alist
spec:
ingressClassName: nginx
tls:
- hosts:
- alist.tianxiang.love
secretName: alist-tls-secret
rules:
- host: alist.tianxiang.love
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: alist-service
port:
number: 80创建 secret 证书
[root@k8s-master1 alist-server]# kubectl -n k8s-app create secret tls alist-tls-secret --cert=公钥.cert --key=私钥.key 启动服务
[root@k8s-master1 alist-server]# kubectl apply -f deployment.yaml
[root@k8s-master1 alist-server]# kubectl get pod -n k8s-app -l app=alist
NAME READY STATUS RESTARTS AGE
alist-d7687445d-64xhz 1/1 Running 0 37h
[root@k8s-master1 alist-server]# kubectl get svc -n k8s-app -l app=alist
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
alist-service ClusterIP 10.96.137.108 <none> 80/TCP 43h
[root@k8s-master1 alist-server]# kubectl get ingress -n k8s-app -l app=alist
NAME CLASS HOSTS ADDRESS PORTS AGE
alist-ingress nginx alist.tianxiang.love 10.96.5.64 80, 443 43h查看服务日志获取初始登陆密码
[root@k8s-master1 alist-server]# kubectl -n k8s-app logs -f alist-57c4b9d7c4-sgcgz访问页面查看

2. 部署 OnlyOffice 服务
OnlyOffice 是一款强大的在线文档协作套件,支持 Word、Excel、PPT 等多种格式,广泛应用于企业私有云和协作平台。本教程将手把手教你如何在本地或服务器上快速部署 OnlyOffice Document Server,并实现基本的安全配置和常见问题排查。
[root@k8s-master1 onlyoffice]# cat onlyoffice-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: onlyoffice-ds
namespace: k8s-app
labels:
app: onlyoffice-ds
spec:
replicas: 1
selector:
matchLabels:
app: onlyoffice-ds
template:
metadata:
labels:
app: onlyoffice-ds
spec:
# 配置 pod 内的 hosts 解析,当然你也可以在 coredns 中配置解析
hostAliases:
- ip: "192.168.233.246"
hostnames:
- "onlyoffice.tianxiang.love"
- "onlyoffice-nginx.tianxiang.love"
- "alist.tianxiang.love"
containers:
- name: onlyoffice-ds
image: registry.cn-hangzhou.aliyuncs.com/tianxiang_app/documentserver:7.1.1
env:
# 关键:设置 OnlyOffice 忽略 SSL 证书验证
- name: USE_UNAUTHORIZED_STORAGE
value: "true"
# JWT 配置
- name: JWT_ENABLED
value: "false"
- name: JWT_SECRET
value: "secret"
# 其他环境变量
- name: FRAME_ANCESTORS
value: "https://onlyoffice-nginx.tianxiang.love:30443 https://onlyoffice.tianxiang.love:30443"
- name: WOPI_ENABLED
value: "false"
ports:
- containerPort: 80
volumeMounts:
- name: data-volume
mountPath: /var/www/onlyoffice/Data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "2Gi"
cpu: "1000m"
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: onlyoffice-pvc
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
labels:
app: onlyoffice-ds
name: onlyoffice-service
namespace: k8s-app
spec:
selector:
app: onlyoffice-ds
ports:
- port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
labels:
app: onlyoffice-service
name: onlyoffice-ingress
namespace: k8s-app
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Permissions-Policy "autoplay=*, camera=*, fullscreen=*, microphone=*, display-capture=*" always;
add_header Feature-Policy "autoplay *; camera *; fullscreen *; microphone *; display-capture *" always;
add_header X-Frame-Options "ALLOWALL" always;
spec:
ingressClassName: nginx
tls:
- hosts:
- onlyoffice.tianxiang.love
secretName: onlyoffice-tls-secret
rules:
- host: onlyoffice.tianxiang.love
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: onlyoffice-service
port:
number: 80
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: onlyoffice-ds
name: onlyoffice-pvc
namespace: k8s-app
spec:
storageClassName: "nfs-provisioner-storage"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi检查服务启动
[root@k8s-master1 onlyoffice]# kubectl get pod -n k8s-app -l app=onlyoffice-ds
NAME READY STATUS RESTARTS AGE
onlyoffice-ds-6f9955c79b-ltq7l 1/1 Running 0 18h页面访问测试

3. 部署 OnlyOffice-nginx
OnlyOffice DocumentServer 无法离开 nginx,它是 DocumentServer 的对外服务入口、静态资源托管、WebSocket 转发、HTTPS 终端、CORS 管理、负载均衡器。Alist 服务配置 onlyoffice-nginx 的域名,而不是直接配置 onlyoffice 的域名。
如下配置文件中,请注意审查所引用的 https://域名:30443 如果你要部署属于自己的 alist onlyoffice onlyoffice-nginx 那么请您替换为自己的域名
[root@k8s-master1 onlyoffice]# cat onlyoffice-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: onlyoffice-nginx
namespace: k8s-app
labels:
app: onlyoffice-nginx
spec:
replicas: 1
selector:
matchLabels:
app: onlyoffice-nginx
template:
metadata:
labels:
app: onlyoffice-nginx
spec:
hostAliases:
- ip: "192.168.233.246"
hostnames:
- "onlyoffice.tianxiang.love"
- "onlyoffice-nginx.tianxiang.love"
- "alist.tianxiang.love"
containers:
- name: onlyoffice-nginx
image: harbor.tianxiang.love:30443/library/nginx:alpine
command:
- sh
- -c
- |
# 添加自签名证书
update-ca-certificates
# 启动 nginx
nginx -g "daemon off;"
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
- name: nginx-html
mountPath: /usr/share/nginx/html
- name: php-files
mountPath: /var/www/html
- name: onlyoffice-ca
mountPath: /usr/local/share/ca-certificates/ca.crt
subPath: ca.crt
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
- name: php-fpm
image: harbor.tianxiang.love:30443/library/php:8.2-fpm-alpine
securityContext:
runAsUser: 0
command:
- sh
- -c
- |
# 添加自签名证书
update-ca-certificates
# 启动 php-fpm
php-fpm
volumeMounts:
- name: php-files
mountPath: /var/www/html
- name: onlyoffice-ca
mountPath: /usr/local/share/ca-certificates/ca.crt
subPath: ca.crt
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
volumes:
- name: nginx-config
configMap:
name: nginx-config
- name: nginx-html
configMap:
name: nginx-html
- name: php-files
configMap:
name: php-files
- name: onlyoffice-ca
configMap:
name: onlyoffice-ca
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
labels:
app: onlyoffice-nginx
name: onlyoffice-nginx
namespace: k8s-app
spec:
selector:
app: onlyoffice-nginx
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
labels:
app: onlyoffice-nginx
name: onlyoffice-nginx
namespace: k8s-app
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Permissions-Policy "autoplay=*, camera=*, fullscreen=*, microphone=*, display-capture=*" always;
add_header Feature-Policy "autoplay *; camera *; fullscreen *; microphone *; display-capture *" always;
add_header X-Frame-Options "ALLOWALL" always;
spec:
ingressClassName: nginx
tls:
- hosts:
- onlyoffice-nginx.tianxiang.love
secretName: onlyoffice-nginx-tls-secret
rules:
- host: onlyoffice-nginx.tianxiang.love
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: onlyoffice-nginx
port:
number: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
name: onlyoffice-ca
namespace: k8s-app
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
MIIFozCCA4ugAwIBAgIJAITwpMXGZfZCMA0GCSqGSIb3DQEBDQUAMGgxCzAJBgNV
BAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW5nMRMwEQYD
VQQKDApFeGFtcGxlIENBMQswCQYDVQQLDAJDQTETMBEGA1UEAwwKTXkgUm9vdCBD
QTAeFw0yNTA2MzAwNjUzNTVaFw0zNTA2MjgwNjUzNTVaMGgxCzAJBgNVBAYTAkNO
MRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW5nMRMwEQYDVQQKDApF
eGFtcGxlIENBMQswCQYDVQQLDAJDQTETMBEGA1UEAwwKTXkgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK0BSNb2CSx81m8itcWPLh11YJ1u
uejzCBqg/voRpBG+zru7eDDGj7pmk/9Nm1cnY+TcxZSqRi5KTCEvrPWwjwSZKwsx
o8W433y7GJelxNUXtJcAuFr5T0DGS4ecMc+QO7j2a4+JFORUEBDZTLRo+QrLjUCP
88cnEDAGkDkYIyzEpNMmU9HJjc9ZZk61tJEqfwygwt0F5ALA9cyyTcm1y6UqQZhG
lUJFdJ9xjZp+h1AnC40ic13etYow6P7a+C7lBMprcE+GMvKlMGO8JoRK5i3smd1u
/fqsr0kD9XDg309aZPXi0vftlyybkTybhurmItAcRUTUVQFPhPagVId6rRa3jHj4
5x7BqvxKcsNOvVO1IucaRtJegO/lo5kOJxRf6jtnAP4cpAJIoUo6LkPQ3Vxn3at7
b8WvNDicYs+bEFTAeKjxJNVI/y7a9LUssNZtTvcy9qFDoq71Xwrdl069pOIgui2L
M8nGP3tbTM/0X6+aq9GgXHuMjxY5uyCk8sDU2DnkFz5Fz014Rc3Fswsl1ewa2qvW
NO8lxPTBjOWkswTTblv5k+Fdne41wp38Qj/C+HSYC8GrQJG6Y+0I42y7zcTE/81s
PGB/3nTDmpGsnRs5+m5b0mvPoI8TKqYj1OkYGH2SbzPWkeidG93uoS0FzHdUINbm
94TB6yX6zkHPa4SdAgMBAAGjUDBOMB0GA1UdDgQWBBSg+Eh72jFxToroSSUFtPcn
Q+SzVTAfBgNVHSMEGDAWgBSg+Eh72jFxToroSSUFtPcnQ+SzVTAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBDQUAA4ICAQBq6KZjTtoGLbkIDIqAXc91ZGBrLOOrMHjh
ynwZI+1rvwzz/egCUb1W1lTfFy/d2OUtr+bGKCWZrIL8PzeWVofCP9edOxpTri/s
jCTscGuHN532ysk6Zx2z6QV7AO/Pe4AzbxgvOLySOPySOd/wkuXqD9dI38O0aJ3Y
SZyIiA5mZYPZ0YZJ7QKRKT+B4aqfN+fWQelrqAfJEwBVKP/GbiDxav+4Yvu4/kvY
jrcE+TCzBCRov/pNr0R/SFgg8qG/aVsGPYu7rsw7Y/yvrRqMid55/CHPlUXoCooH
zFYPqbzIa5xOlrrHFAdUuUQY0bE/diHi89XK8BArdI3iTvtk2HensANi/1rk5t6z
5Hr+cJP01gNbELOHQTJ8Qyf4BstMgByeO2cf+xDJIyYveVAMp/6+HoRKZPMD/x7P
vGM0PqT7jYQXUCQpS1jG0vDixLDnf/VPhGooDGKwn87IbscqQsBJDr5Er5ydbSn5
gASKNBn1WICki6Gs/qq6iOvfR+fDR9xsVt57Ry3FuLnP91Q4gJuzzc3AK74ip63V
8EKDrhQd5XAnVaoULaIch9U0vis/Q27qUXzK+Vu5KfgQlmqKceHfse5gyMwk42tB
YLv/fKXvvb5V9Ov85VMoyk6O9muhVVG3S5gDF807ls3NPDtSvhUdVL1VghVwoECN
ZmqzUcx+jg==
-----END CERTIFICATE-----
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
namespace: k8s-app
data:
default.conf: |
server {
listen 80;
server_name onlyoffice-nginx.tianxiang.love;
location / {
root /usr/share/nginx/html;
index view.html edit.html;
try_files $uri $uri/ =404;
}
location ~ \.php$ {
# 修正:指向本地 PHP-FPM 容器
fastcgi_pass 127.0.0.1:9000;
# 修正:添加必要的 fastcgi 参数
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT /var/www/html;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
include fastcgi_params;
}
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-html
namespace: k8s-app
data:
view.html: |
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>OnlyOffice Viewer</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
#placeholder {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.info-box {
font-family: Arial, sans-serif;
padding: 40px;
}
.info-box h1 {
color: #0066cc;
}
.info-box code {
background: #f4f4f4;
padding: 6px 10px;
border-radius: 6px;
display: inline-block;
margin-top: 10px;
}
.info-box .section {
margin-top: 20px;
padding: 15px;
background: #fafafa;
border-radius: 8px;
border: 1px solid #eee;
}
</style>
</head>
<body>
<div id="placeholder"></div>
<script type="text/javascript"
src="https://onlyoffice.tianxiang.love:30443/web-apps/apps/api/documents/api.js"></script>
<script>
function getQueryParamValue(name) {
const searchParams = new URLSearchParams(window.location.search);
return searchParams.get(name);
}
const url = decodeURIComponent(getQueryParamValue("src") || "");
// 如果用户 GET 访问并且没有 src 参数 → 显示提示页面
if (!url) {
document.body.innerHTML = `
<div class="info-box">
<h1>OnlyOffice 在线预览服务</h1>
<p>你正在访问一个用于 <b>在线预览 Office 文档</b> 的页面。</p>
<div class="section">
<h3>📌 用法说明</h3>
<p>你需要在 URL 中通过 <code>?src=文件URL</code> 指定要预览的文件。</p>
<p>示例:</p>
<code>
https://onlyoffice-nginx.tianxiang.love:30443/view.html?src=https://example.com/test.docx
</code>
</div>
<div class="section">
<h3>🛠 当前配置说明</h3>
<ul>
<li>模式:<b>只读预览(view mode)</b></li>
<li>允许下载:✔</li>
<li>允许打印:✔</li>
<li>允许评论:✔</li>
<li>允许表单填写:✔</li>
<li>禁止编辑:✘</li>
<li>OnlyOffice DocumentServer 地址:<br>
<code>https://onlyoffice.tianxiang.love:30443</code>
</li>
</ul>
</div>
<div class="section">
<h3>📄 支持文件类型</h3>
<p>docx / xlsx / pptx / pdf / txt / csv 等</p>
</div>
<p style="margin-top:30px;color:#999">如果你看到这个页面,说明当前没有指定要预览的文档。</p>
</div>
`;
} else {
// 否则加载 OnlyOffice
const fileName = url.substring(url.lastIndexOf('/') + 1, url.includes('?') ? url.lastIndexOf('?') : url.length);
const fileExtension = fileName.split('.').pop();
const docEditor = new DocsAPI.DocEditor("placeholder", {
"document": {
"fileType": fileExtension,
"permissions": {
"edit": false,
"comment": true,
"download": true,
"print": true,
"fillForms": true,
},
"title": fileName,
"url": url,
},
"editorConfig": {
"lang": "zh",
"mode": "view",
},
"height": "100%",
"type": "embedded",
});
}
</script>
</body>
</html>
edit.html: |
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>OnlyOffice Editor</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
#placeholder {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.info-box {
font-family: Arial, sans-serif;
padding: 40px;
}
.info-box h1 {
color: #0066cc;
}
.info-box code {
background: #f4f4f4;
padding: 6px 10px;
border-radius: 6px;
display: inline-block;
margin-top: 10px;
}
.info-box .section {
margin-top: 20px;
padding: 15px;
background: #fafafa;
border-radius: 8px;
border: 1px solid #eee;
}
</style>
</head>
<body>
<div id="placeholder"></div>
<script type="text/javascript"
src="https://onlyoffice.tianxiang.love:30443/web-apps/apps/api/documents/api.js"></script>
<script>
function getQueryParamValue(name) {
const searchParams = new URLSearchParams(window.location.search);
return searchParams.get(name);
}
const url = decodeURIComponent(getQueryParamValue("src") || "");
// GET 访问无参数 → 显示提示页面
if (!url) {
document.body.innerHTML = `
<div class="info-box">
<h1>OnlyOffice 在线编辑服务</h1>
<p>此页面用于 <b>在线编辑 Office 文档</b> (Word / Excel / PPT)。</p>
<div class="section">
<h3>📌 使用方法</h3>
<p>通过 URL 传递要编辑的文件地址:</p>
<code>
https://onlyoffice-nginx.tianxiang.love:30443/edit.html?src=https://example.com/demo.docx
</code>
</div>
<div class="section">
<h3>🛠 当前编辑配置</h3>
<ul>
<li>模式:<b>编辑模式(edit)</b></li>
<li>允许编辑:✔</li>
<li>允许注释:✔</li>
<li>允许下载:✔</li>
<li>允许打印:✔</li>
<li>允许表单填写:✔</li>
<li>允许修订(Review):✔</li>
<li>forcesave:开启 ✔</li>
<li>回调地址:<br>
<code>https://onlyoffice-nginx.tianxiang.love:30443/callback.php</code>
</li>
</ul>
</div>
<div class="section">
<h3>📄 支持文件类型</h3>
<p>docx、xlsx、pptx、csv、docm、dotx 等现代格式</p>
</div>
<p style="margin-top:30px;color:#999">如果你看到这个页面,说明当前没有指定要编辑的文件。</p>
</div>
`;
} else {
// 正常加载 OnlyOffice 编辑器
const fileName = url.substring(url.lastIndexOf('/') + 1, url.includes('?') ? url.lastIndexOf('?') : url.length);
const fileExtension = fileName.split('.').pop();
function generateDocumentKey(url) {
const allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._=';
const fileName = url.substring(url.lastIndexOf('/') + 1);
const timestamp = new Date().toISOString();
let key = fileName + timestamp;
key = encodeURIComponent(key).substr(0, 20);
key = key.split('').filter(char => allowedChars.includes(char)).join('');
return key;
}
const docEditor = new DocsAPI.DocEditor("placeholder", {
"document": {
"fileType": fileExtension,
"title": fileName,
"url": url,
"key": generateDocumentKey(url),
"permissions": {
"edit": true,
"comment": true,
"download": true,
"print": true,
"fillForms": true,
"review": true,
"copy": true,
"modifyContentControl": true,
"modifyFilter": true,
"export": true
}
},
"editorConfig": {
"lang": "zh",
"region": "zh-CN",
"mode": "edit",
"callbackUrl": `https://onlyoffice-nginx.tianxiang.love:30443/callback.php?file_url=${encodeURIComponent(url)}`,
"callbacks": {
"onError": (error) => console.error("OnlyOffice 错误:", error),
"onReady": () => console.log("OnlyOffice 编辑器已加载")
},
"customization": {
"forcesave": true,
"compactHeader": false,
"showReviewChanges": true,
"hideRightMenu": false,
"spellcheck": false,
"features": {
"spellcheck": { "mode": false }
}
}
},
"height": "100%",
"type": "desktop"
});
}
</script>
</body>
</html>
---
apiVersion: v1
kind: ConfigMap
metadata:
name: php-files
namespace: k8s-app
data:
callback.php: |
<?php
error_reporting(E_ALL & ~E_WARNING);
ini_set('display_errors', 0);
// Alist 登陆配置
$config = [
'alist' => [
'base_url' => 'https://alist.tianxiang.love:30443',
'username' => 'admin',
'password' => 'password',
],
];
$cached_token = null;
$token_expires = 0;
// 封装的 curl POST 请求函数
function curl_post($url, $header, $data) {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_SSL_VERIFYHOST => FALSE,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $data,
CURLOPT_HTTPHEADER => $header,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 30
]);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
// 封装的 curl PUT 请求函数
function curl_put($url, $header, $data) {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_SSL_VERIFYHOST => FALSE,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_HTTPHEADER => array_map('trim', $header),
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HEADER => true
]);
$output = curl_exec($curl);
$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$body = substr($output, $header_size);
curl_close($curl);
return $body;
}
// 封装的 curl GET 请求函数(用于下载文件)
function curl_get_contents($url) {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_SSL_VERIFYHOST => FALSE,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 30
]);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
// 添加新的 curl GET 请求函数
function curl_get($url, $header) {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_SSL_VERIFYHOST => FALSE,
CURLOPT_HTTPHEADER => array_map('trim', $header),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
// 检查 file_url 参数
if (!isset($_GET['file_url'])) {
http_response_code(400);
echo json_encode(["error" => 8, "message" => "Missing 'file_url' parameter"]);
exit;
}
$input_file_url = $_GET['file_url'];
$path = parse_url($input_file_url, PHP_URL_PATH);
if ($path === false || empty($path)) {
http_response_code(400);
echo json_encode(["error" => 10, "message" => "Invalid file_url"]);
exit;
}
// 不知为啥我这回传会多一个p文件夹,移除开头的斜杠,避免路径问题
$path = ltrim($path, '/');
if (strpos($path, 'p/') === 0) {
$path = substr($path, 2);
}
$filename = $path;
// 读取请求体
if (($body_stream = file_get_contents("php://input")) === FALSE) {
http_response_code(500);
echo json_encode(["error" => 1, "message" => "Failed to read input stream"]);
exit;
}
// 解析 JSON
$data = json_decode($body_stream, TRUE);
if ($data === null) {
http_response_code(400);
echo json_encode(["error" => 7, "message" => "Invalid JSON data"]);
exit;
}
// 检查 status 是否存在
if (!isset($data["status"])) {
http_response_code(400);
echo json_encode(["error" => 9, "message" => "Missing 'status' parameter"]);
exit;
}
// 获取 Alist token 的函数
function get_alist_token($config) {
global $cached_token, $token_expires;
$current_time = time();
if ($cached_token && $current_time < $token_expires) {
return $cached_token;
}
// 检查 curl 扩展是否可用
if (!function_exists('curl_init')) {
error_log("PHP curl extension is not available");
return null;
}
$login_url = $config['alist']['base_url'] . "/api/auth/login";
$login_data = json_encode([
"username" => $config['alist']['username'],
"password" => $config['alist']['password']
]);
$post_response = curl_post(
$login_url,
["Content-Type: application/json"],
$login_data
);
if ($post_response === FALSE || empty($post_response)) {
error_log("Failed to get response from Alist API: " . $login_url);
return null;
}
$post_response_data = json_decode($post_response, TRUE);
if ($post_response_data === null) {
error_log("Failed to decode Alist API response: " . $post_response);
return null;
}
if (!isset($post_response_data["data"]["token"])) {
error_log("Token not found in Alist API response: " . json_encode($post_response_data));
return null;
}
$cached_token = $post_response_data["data"]["token"];
$token_expires = $post_response_data["data"]["exp"] ?? $current_time + 300;
return $cached_token;
}
// 保存文档的通用函数
function save_document($data, $config, $input_file_url) {
$downloadUri = $data["url"];
if (($new_data = curl_get_contents($downloadUri)) === FALSE) {
return ["error" => 2, "message" => "Failed to download file"];
}
// 使用全局变量 $filename,避免重复解析路径
global $filename;
$path = $filename;
$max_retries = 3;
for ($retry = 0; $retry < $max_retries; $retry++) {
$token = get_alist_token($config);
if ($token === null) {
return ["error" => 3, "message" => "Failed to get token"];
}
$token = trim($token);
$put_response = curl_put(
$config['alist']['base_url'] . "/api/fs/put",
[
"Authorization: " . trim($token),
"File-Path: " . $path,
"As-Task: true",
"Content-Length: " . strlen($new_data),
"Content-Type: application/octet-stream"
],
$new_data
);
if ($put_response === FALSE) {
return ["error" => 4, "message" => "Failed to upload file"];
}
$put_response_data = json_decode($put_response, TRUE);
if ($put_response_data["code"] === 401) {
// 清除缓存的令牌
global $cached_token, $token_expires;
$cached_token = null;
$token_expires = 0;
if ($retry < $max_retries - 1) {
sleep(5);
continue;
}
return ["error" => 3, "message" => "Token is invalidated after multiple retries"];
}
if (isset($put_response_data["data"]["task"]["id"])) {
$task_id = $put_response_data["data"]["task"]["id"];
$max_wait = 60;
$wait_interval = 2;
$start_time = time();
for ($i = 0; $i < $max_wait; $i += $wait_interval) {
$task_response = curl_get(
$config['alist']['base_url'] . "/api/task/" . $task_id,
["Authorization: " . trim($token)]
);
$task_data = json_decode($task_response, TRUE);
if ($task_data["code"] === 200) {
$task_status = $task_data["data"]["status"] ?? "unknown";
if ($task_status === "finished") {
return ["error" => 0, "message" => "Success"];
} elseif ($task_status === "error") {
$error_msg = $task_data["data"]["error"] ?? "Unknown error";
return ["error" => 4, "message" => "Upload task failed: " . $error_msg];
}
}
sleep($wait_interval);
}
return ["error" => 4, "message" => "Upload task timed out"];
}
return ["error" => 0, "message" => "Success"];
}
return ["error" => 3, "message" => "Failed to upload file after multiple retries"];
}
// 处理不同状态码
switch ($data["status"]) {
case 1: // 文档已打开
$token = get_alist_token($config);
if ($token === null) {
http_response_code(500);
echo json_encode(["error" => 3, "message" => "Failed to get token"]);
} else {
http_response_code(200);
echo json_encode(["error" => 0, "message" => "Document opened"]);
}
break;
case 2: // 保存事件
case 6: // 文档关闭
$result = save_document($data, $config, $input_file_url);
if ($result["error"] !== 0) {
http_response_code(500);
echo json_encode($result);
} else {
http_response_code(200);
echo json_encode($result);
}
break;
default:
http_response_code(500);
echo json_encode(["error" => 5, "message" => "Unknown status code"]);
break;
}
?>检查服务启动
[root@k8s-master1 onlyoffice]# kubectl get pod -n k8s-app -l app=onlyoffice-nginx
NAME READY STATUS RESTARTS AGE
onlyoffice-nginx-75c557d694-p4ljw 2/2 Running 0 16h三、配置 Alist 预览
1. 添加配置
"Onlyoffice View":"https://onlyoffice-nginx.tianxiang.love:30443/view.html?src=$e_url",
"Onlyoffice Edit":"https://onlyoffice-nginx.tianxiang.love:30443/edit.html?src=$e_url",
测试打开办公文件

