avatar

甄天祥-Linux-个人小站

A text-focused Halo theme

  • 首页
  • 分类
  • 标签
  • 关于
Home Kubernetes 安装部署 Alist 并配置 Onlyoffice
文章

Kubernetes 安装部署 Alist 并配置 Onlyoffice

Posted 23 days ago Updated 5 days ago
By Administrator
102~132 min read

一、简单介绍

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

1-ZzTR.png

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-sFKo.png

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-mNoU.png

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-----
    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
    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",

4-oJdk.png

  • 测试打开办公文件

5-Czus.png

6-JVTX.png

云原生与容器技术
kubernetes
License:  CC BY 4.0
Share

Further Reading

Nov 19, 2025

Kubernetes 安装部署 Alist 并配置 Onlyoffice

Alist 是一个支持多种存储的目录列表程序,能够将网盘、对象存储和本地存储等挂载为统一目录,提供文件浏览、管理和分享功能。它支持 OneDrive、Google Drive、阿里云盘、百度网盘等多种存储方式,界面简洁美观,基于 Material Design 设计。Alist 功能强大,包括文件预览、下载、分享、搜索和权限管理等,并且开源免费。部署 Alist 服务可以通过 Docker、宝塔面板或直接运行等方式实现,文中以 K8S 部署为例,详细介绍了配置步骤及 OnlyOffice 的集成方法,用于在线预览和编辑 Office 文档。此外,还提供了如何通过 HTTPS 和自签名证书确保服务安全访问的指导。

Oct 23, 2025

KubeSphere-04-Dev-ops 流水线插件的使用

KubeSphere 基于 Jenkins 的 DevOps 系统专为 Kubernetes 中的 CI/CD 工作流设计,提供了一站式的解决方案,包括插件管理、Binary-to-Image (B2I)、Source-to-Image (S2I)等功能。该系统兼容第三方私有镜像仓库和代码库,提供了全面的可视化 CI/CD 流水线。本文档指导用户开启 KubeSphere 的 DevOps 插件,规划流水线并编写 Jenkinsfile,通过实战案例让用户掌握从理论到实践的全过程。文档详细介绍了如何解决开启 DevOps 组件时可能遇到的问题、配置步骤以及验证方法,并演示了创建和管理 DevOps 项目的过程,涵盖用户创建、企业空间与项目的建立等。此外,还提供了简化版的 DevOps 流水线设计示例,涉及从源代码检出到部署环境的整个流程,包括单元测试、编译、构建推送镜像及多环境部署策略。最后,通过一系列准备工作的说明和实际操作步骤,确保用户能够顺利实现自动化持续集成和部署。

Oct 14, 2025

KubeSphere-03-Logging 日志插件的使用

KubeSphere Logging 是 KubeSphere 平台的日志管理插件,基于 Elasticsearch 或 OpenSearch 构建,支持多租户日志收集、查询和分析。它自动采集容器、工作负载及平台审计日志,并通过 Fluent Bit 进行预处理。该插件提供直观的查询界面、灵活的日志保留策略(默认7天)、Sidecar模式增强可靠性以及外部日志系统集成等功能,帮助企业快速定位问题并满足合规要求。开启插件需编辑 ks-installer 配置文件以选择性启用 Elasticsearch 或 OpenSearch,并设置相关参数。此外,还介绍了一款基于 Go 的 OpenSearch 告警与可视化系统,支持多种通知渠道,可通过本地构建 Docker 镜像并在 Kubernetes 环境中部署使用。

OLDER

KubeSphere-04-Dev-ops 流水线插件的使用

NEWER

Recently Updated

  • Kubernetes 安装部署 Alist 并配置 Onlyoffice
  • KubeSphere-04-Dev-ops 流水线插件的使用
  • KubeSphere-03-Logging 日志插件的使用
  • KubeSphere-02-Service Mesh 的使用
  • KubeSphere-01-介绍与基础使用

Trending Tags

KVM Service Mesh Docker shell 路由规则 Mysql Containerd GitOps 网络设备 Prometheus

Contents

©2025 甄天祥-Linux-个人小站. Some rights reserved.

Using the Halo theme Chirpy