flytachi/winter-s4w-api 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

flytachi/winter-s4w-api

Composer 安装命令:

composer require flytachi/winter-s4w-api

包简介

S4W file storage API client for the Winter framework — upload, list, fetch, delete and private streaming proxy.

README 文档

README

Latest Version on Packagist Software License PHP

PHP client for S4W — a self-hosted file storage service — built on top of the Winter Framework HTTP layer (flytachi/winter-cast).

It wraps the S4W REST API behind a small, typed facade: upload, list, fetch and delete files, build private/public URLs, and stream private files straight through your backend to the browser (with HTTP Range / seek support) without ever exposing the master token.

$file = S4w::fileUpload('/tmp/invoice.pdf', section: 'documents');

$file->id;        // "692320e4-ebaa-49ae-8399-abced0ea44c8"
$file->mime;      // "application/pdf"
$file->isPublic;  // false

What is S4W?

S4W is a standalone file-storage server (deduplicating object store with public and private access, sections, image compression / WebP conversion).

docker run -d --name s4w -p 9090:80 flytachi/s4w

This package is the client; it talks to a running S4W instance over HTTP.

Requirements

Installation

composer require flytachi/winter-s4w-api

Configuration

The package ships an abstract base class, S4wBaseApi. You create one concrete subclass that supplies the connection details. This keeps credentials in your app (env, config, secrets manager) and lets you have multiple S4W connections if needed.

<?php

namespace App\Services;

use Flytachi\Winter\S4w\S4wBaseApi;

class S4w extends S4wBaseApi
{
    protected static function baseUrl(): string
    {
        return env('S4W_URL');        // e.g. https://files.example.com
    }

    protected static function token(): string
    {
        return env('S4W_TOKEN');      // master/instance token (server-to-server only)
    }

    protected static function instance(): string
    {
        return env('S4W_INSTANCE');   // instance slug, used to build PUBLIC urls
    }
}
# .env
S4W_URL=https://files.example.com
S4W_TOKEN=your-instance-master-token
S4W_INSTANCE=my-app
Method Required for Notes
baseUrl() everything Base URL of the S4W instance. Trailing slash optional.
token() everything Bearer token. Server-to-server only — never sent to the browser.
instance() generateUrls*() only Public-URL slug. Throws if empty when building public URLs.

The token() is your master key to the instance. The whole point of streamPrivate() is that private files reach the user through your backend, so this token never leaves the server.

Concepts

  • Section — an optional namespace/folder for files (documents, avatars, …). Sections can be public or private (sectionList() reports public).
  • Private vs public access:
    • Private/p/[section/]{id} — requires the token. Served by your backend via streamPrivate() after you authorize the request.
    • Public/o/{instance}/[section/]{id} — directly reachable, no token.
  • S4wFile — immutable DTO returned by upload/get/list.
  • PResult / PMeta — page-centric pagination envelope ({ meta, data }).

API reference

All methods are static and called on your subclass (S4w below).

sectionList(): array

Returns the list of sections: [{ name: string, public: bool }, …] (or []).

$sections = S4w::sectionList();
// [['name' => 'documents', 'public' => false], ['name' => 'avatars', 'public' => true]]

fileList(int $limit = 20, int $page = 1, string $section = '', string $search = ''): PResult<S4wFile>

Paginated listing. Empty $section / $search are omitted from the query.

$result = S4w::fileList(limit: 50, page: 1, section: 'documents', search: 'invoice');

$result->meta->total;   // 134
$result->meta->pages;   // 3
$result->meta->next;    // 2 | null
foreach ($result->data as $file) {
    echo $file->name;   // each is an S4wFile
}

fileUpload(string $filePath, string $name = '', string $section = '', ?int $compress = null, bool $webp = false): S4wFile

Uploads a local file (multipart). Throws InvalidArgumentException if the path is not a readable file, or if $compress is outside 0..100.

$file = S4w::fileUpload(
    filePath: '/tmp/photo.png',
    name:     'avatar.png',   // optional override of the stored name
    section:  'avatars',
    compress: 80,             // 0..100, optional
    webp:     true,           // convert to WebP
);

fileGet(string $id): S4wFile

Fetches a single file's metadata.

$file = S4w::fileGet('692320e4-ebaa-49ae-8399-abced0ea44c8');

fileDelete(string $id): void

Deletes a file.

S4w::fileDelete('692320e4-ebaa-49ae-8399-abced0ea44c8');

generateUrls(string ...$ids): array<string, S4wUrls>

Builds private + public URLs for one or more ids, keyed by id. Requires instance() to be set (for the public URL).

$urls = S4w::generateUrls('id-a', 'id-b');

$urls['id-a']->privateUrl;   // https://files.example.com/p/id-a
$urls['id-a']->publicUrl;    // https://files.example.com/o/my-app/id-a

generateSectionUrls(string $section, string ...$ids): array<string, S4wUrls>

Same, but scoped to a section.

$urls = S4w::generateSectionUrls('documents', 'id-a');

$urls['id-a']->privateUrl;   // …/p/documents/id-a
$urls['id-a']->publicUrl;    // …/o/my-app/documents/id-a

streamPrivate(string $id, string $section = ''): never

Transparently proxies a private S4W file to the current HTTP client.

It fetches /p/[section/]{id} server-to-server under the instance token and streams the body in chunks straight to php://output — no memory buffering, no temp file. It forwards the upstream status (200 / 206 / 304), a safe whitelist of headers, and the client's Range header (video seek / resumable downloads).

S4w::streamPrivate('692320e4-…', 'documents');

⚠️ Important — this method writes the response and calls exit.

  • Call it only after you have authorized the current user for this file.
  • The token stays server-side; it is never exposed to the browser.
  • Designed for PHP-FPM. It will not work under Swoole (no echo/exit output model).
  • On upstream failure it emits a generic 404 (not found) or 502 (upstream unavailable) JSON body — no upstream details leak.

Streaming a private file (full example)

A controller route that checks ownership, then hands off the byte-stream:

#[GetMapping('/files/{id}')]
public function download(string $id): never
{
    $file = $this->repo->findUserFile(auth()->id(), $id)
        ?? throw new NotFoundException();   // your own authorization

    // From here on the response belongs to S4W. This call exits.
    S4w::streamPrivate($file->s4wId, $file->section);
}

Because Range is forwarded, an HTML <video controls src="/files/{id}"> will seek correctly, and browsers can resume interrupted downloads.

Data objects

S4wFile (readonly)

Property Type Description
id string File id (UUID).
name string Stored file name.
section ?string Section, or null.
mime string MIME type.
size string Size (as reported by S4W).
extension string File extension.
hash string Content hash (used for deduplication).
deduplicated bool Whether the content was deduplicated on store.
isPublic bool Whether the file is publicly accessible.
createdAt string Creation timestamp.
privateUrl ?string Private URL, if provided by the API.
publicUrl ?string Public URL, if provided by the API.

Helper: $file->isImage()true for image/jpeg, image/png, image/gif, image/webp.

S4wUrls (readonly)

Property Type
privateUrl ?string
publicUrl ?string

PResult<TItem> / PMeta

PResult is { meta: PMeta, data: TItem[] } and is JsonSerializable.

PMeta fields: current, size, total, pages, previous (?int), next (?int).

Error handling

API errors (non-2xx from S4W) raise Flytachi\Winter\Cast\Exception\RequestException, carrying the response and the extracted error message. Input validation (fileUpload) raises InvalidArgumentException. streamPrivate() does not throw — it terminates the request with the appropriate HTTP status.

use Flytachi\Winter\Cast\Exception\RequestException;

try {
    $file = S4w::fileGet($id);
} catch (RequestException $e) {
    // $e->getMessage(), inspect the response, etc.
}

License

MIT — Flytachi

统计信息

  • 总下载量: 1
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 3
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-22

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固