MENU

图床外显为Webdav管理

2025 年 12 月 28 日 • 阅读: 138 • 技术,开发

此处内容需要评论回复后方可阅读

图床支持的储存方案
使用兰空图床LskyPro+,支持的储存方案,支持自建的只有Webdav和S3兼容,如果想做一个聚合图床,最好的办法就是用Webdav。

而像Github等能够部署到Openlist(Alist)然后外显为Webdav管理,可一些只支持API或者没有API的图床怎么办?

可以试试我搭建的图床,支持SM.MS,OOXX,以及国内CDN与对象储存等多种储存方案:LHL‘s Image

SM.MS


SM.MS有公开的API,思路就是写一个Webdav,其中GET、PUT、PROPFIND等请求(Webdav)就可以把API相关内容写进去。sm.ms返回的链接写入数据库,然后就有原文件名-SM.MS关系的记录。我如果访问http://webdav-ip:port/原文件名就会302跳转到sm.ms的url。

可是存在一个问题,Webdav是不支持调回的,也就是我自建的图床并不知道sm.ms的url是多少,如果图片访问过程中多出了一个环节,需要先访问webdav然后再跳转到webdav

于是我写了一个/api/get-url?path=/原图片名的接口,用来获取sm.ms的url(这里由于图床写入数据库时候使用的cleanPath而只会返回cleanPath)

当然需要修改一下图床的源码。在上传图片写入图床数据库之前,对于特定的储存ID在写入path的时候,调用api接口替换原文件名。

请注意项目+版本号,如果需要其他项目或者特定版本的代码可以在评论区回复

LskyPro+ 2.2.3 /app/Services/PhotoService.php
     /**
     * 储存图片信息
     *
     * @param array $data
     * @param array $albums
     * @param array<string> $tags
     * @return Photo
     * @throws Throwable
     */
     public function store(array $data, array $albums = [], array $tags = []): Photo
    {
        return DB::transaction(function () use ($data, $albums, $tags) {
            $originalPathname = $data['pathname'] ?? '';
            $storageId = $data['storage_id'] ?? null;
            $finalPathname = $originalPathname;
            $externalUrl = null;

            // 🔑 直接从 .env 读取 PHOTO_STORAGE_{ID}_GET_URL_API
            if ($storageId && is_numeric($storageId) && $originalPathname !== '') {
                $envKey = 'PHOTO_STORAGE_' . $storageId . '_GET_URL_API';
                $getUrlApi = env($envKey);

                if ($getUrlApi) {
                    try {
                        $fullApiUrl = rtrim($getUrlApi, '/') . '?path=' . urlencode($originalPathname);
                        $response = Http::timeout(5)->get($fullApiUrl);

                        if (
                            $response->successful() &&
                            is_array($body = $response->json()) &&
                            !empty($body['url'])
                        ) {
                            // ✅ 直接使用 clean path(如 "i/AbC123.png")
                            $finalPathname = ltrim((string) $body['url'], '/');
                        }
                    } catch (\Exception $e) {
                        Log::warning('Failed to fetch CDN path from get_url_api', [
                            'storage_id' => $storageId,
                            'pathname' => $originalPathname,
                            'env_key' => $envKey,
                            'error' => $e->getMessage(),
                        ]);
                    }
                }
            }


            // 更新 data
            $data['pathname'] = $finalPathname;
            if ($externalUrl) {
                $data['external_url'] = $externalUrl;
            }

            // 正常存储
            $attributes = Arr::only($data, ['user_id', 'storage_id', 'md5', 'sha1', 'pathname']);
            /** @var Photo $photo */
            $photo = Photo::firstOrCreate($attributes, $data);

            // 相册
            if (count($albums) > 0) {
                $photo->albums()->syncWithoutDetaching($albums);
            }

            // 标
            if ($tags) {
                $attach = [];
                foreach ($tags as $name) {
                    $tag = Tag::firstOrCreate(['name' => $name]);
                    $attach[$tag->id] = ['user_id' => $photo->user_id];
                }
                $photo->tags()->syncWithoutDetaching($attach);
            }

            return $photo;
        });
    }

然后在/.env中写入设置

// 将[StorageID]更改为SM.MS对于的Storage ID,WEBDAV_IP和PORT更换为对应的IP和端口,当然可以使用NGINX等反代从而支持TLS
PHOTO_STORAGE_[StorageID]_GET_URL_API=http://WEBDAV_IP:PORT/api/get-url

图床WebDAV配置设置:

储存目录: /
访问URL: https://i.loli.net (如果是SM.MS Premium,修改为https://cdn.sa.net)
访问前缀: 不用填写
命名填写: 建议只写 {md5}
st=>start: 开始
e=>end: 结束
op1=>operation: Lsky Pro+ / WebDAV 客户端 PUT 请求上传文件
op2=>operation: WebDAV 代理服务接收请求并调用 SM.MS API 上传文件
op3=>operation: SM.MS 图床返回 CDN URL 和删除链接
op4=>operation: WebDAV 代理服务写入映射记录 (原文件名.png ↔ AbC123.png)
op5=>operation: 准备写入数据库前,调用 /api/get-url?path=原文件名.png 获取URL
op6=>operation: 查询 SQLite 中的映射记录
op7=>operation: 返回 URL { "url": "AbC123.png" }
op8=>operation: 存入 pathname = AbC123.png 到 Lsky 数据库
op9=>operation: 用户访问图片 GET https://i.loli.net/AbC123.png
op10=>operation: 直接访问 WebDAV 路径 GET /原文件名.png
op11=>operation: WebDAV 代理服务进行 302 重定向到 https://i.loli.net/AbC123.png

st->op1->op2->op3->op4->op5->op6->op7->op8->e
op9->e
op10->op11->e

OOXX.OOO


由于ooxx没有公开API接口,所以我们需要自己分析一下。上传图片可以发现接口是/upload,但是细看发现上传的时候消息头中有个cookie:_xsrf=2|d8c52d8d|bb375f17f30175601ddd6f0ceebd0d94|1766648051
OOXX API接口
所以我们需要写一个getValidXSRF():

  • 发起 GET 请求到 https://ooxx.ooo/upload
  • 从响应 Cookie 中提取 _xsrf
  • 将 _xsrf 作为字段和 Cookie 同时发送(模拟浏览器行为)

再分析响应:

[{"slug": "NWMwN", "ext": ".png", "filename": "\u622a\u56fe 2025-12-27 21-08-23.png", "delete_token": "8GpOekI_6Wc", "url": "https://i.ooxx.ooo/i/NWMwN.html", "image_url": "https://i.ooxx.ooo/i/NWMwN.png", "preview_url": "http://ooxx.ooo/i/NWMwN.png", "delete_url": "http://ooxx.ooo/i/delete/NWMwN/8GpOekI_6Wc"}]

那么接下来一样的,建立数据库然后将请求相应的内容写入。

项目部署

由于ooxx webdav这个代码写的比较仓促,而且这个没有api token,那么项目配置只有端口和webdav密码,所以我就写死在代码里面了,默认用户admin,密码123456,需要在编译前在项目中修改。

git clone https://github.com/yourname/ooxx-webdav.git
cd ooxx-webdav

找到这一行并修改

if !ok || username != "[email protected]" || password != "YourSecurePassword!" {

编译后运行

go build -o ooxx-webdav .
chmod ./ooxx-webdav
./ooxx-webdav
最后编辑于: 2026 年 01 月 15 日
返回文章列表 打赏
本页链接的二维码
打赏二维码
添加新评论

已有 2 条评论
  1. L.R.T. L.R.T.        

    沙发~ 很实用哦

    1. LHL LHL        

      @L.R.T.@(太开心)