定制 pboivin/flou 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

pboivin/flou

Composer 安装命令:

composer require pboivin/flou

包简介

PHP responsive images and lazy loading toolbox.

README 文档

README

Build Status Latest Stable Version License

Flou is a PHP package integrating Glide (PHP) and vanilla-lazyload (JS). It's optimized to quickly implement lazy loading and responsive images from a local folder of source images.

Features:

  • Transform local images on initial page load (does not expose Glide URLs)
  • Can leverage custom Glide configurations (e.g. source images on S3)
  • Generate responsive HTML for img and picture elements
  • Useable with static site generators and in CLI scripts
  • Framework agnostic (a set of plain PHP classes)

Requirements:

  • PHP >= 8.0

Table of contents:

Demo project:

See the flou-jigsaw-demo repository for an example project integrating Flou with the Jigsaw PHP static site generator.

Installing

The package can be installed via Composer:

composer require pboivin/flou

This also installs Glide as a Composer dependency.

You can pull in the vanilla-lazyload library via a CDN:

<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.min.js"></script>

or via NPM:

npm install --save vanilla-lazyload

Consult the vanilla-lazyload documentation for more installation options.

Getting Started

First, initialize the LazyLoad JS object. Add the following script to your page template:

<script>
    document.addEventListener("DOMContentLoaded", () => {
        new LazyLoad({
            elements_selector: ".lazyload",
        });
    });
</script>

Then, initialize the ImageFactory PHP object with your project-specific configuration:

use Pboivin\Flou\ImageFactory;

$flou = new ImageFactory([
    'sourcePath' => '/home/user/my-site.com/public/images/source',
    'cachePath' => '/home/user/my-site.com/public/images/cache',
    'sourceUrlBase' => '/images/source',
    'cacheUrlBase' => '/images/cache',
]);

Configuration

The required options are:

Name Type Description
sourcePath string The full path to the source images.
cachePath string The full path where Glide will store the image transformations.
sourceUrlBase string The base URL for the source images.
cacheUrlBase string The base URL for the transformed images.

Other options:

Name Type Description
glideParams array Default Glide parameters for LQIP elements.
renderOptions array Default render options for all images.

Framework Integration

If you're using a framework with a Service Container, you can register the $flou instance as a singleton for your entire application. This will be your entry point to transform and render images.

Extra JS and CSS

Some examples below require additional JS and CSS. You'll find a more complete sample in the assets directory.

Image Transformations

Transforming source images

Use the image() method to transform a single image into a low-quality image placeholder (LQIP):

$image = $flou->image('01.jpg');

You can also provide custom Glide parameters for the image transformation:

$image = $flou->image('01.jpg', [
    'w' => 10,
    'h' => 10,
    'fit' => 'crop',
]);

You'll find all available parameters in the Glide documentation.

As you can see, the default parameters are used to generate LQIP from source images, but you are not restricted to that. You may generate as many transformations as you need from the source image:

$phone = $flou->image('01.jpg', ['w' => 500]);
$tablet = $flou->image('01.jpg', ['w' => 900]);
$desktop = $flou->image('01.jpg', ['w' => 1300]);

If you're working with responsive images and the srcset attribute, have a look at the next section (Image Sets).

Default Glide parameters

You can customize the default Glide parameters in the ImageFactory configuration:

$flou = new ImageFactory([
    // ...
    'glideParams' => [
        'h' => 10,
        'fm' => 'gif',
    ],
]);

Image objects

The image() method returns an Image object, from which you can conveniently access the source image file and the transformed (cached) image file:

$image = $flou->image('01.jpg');

# Source image data:
echo $image->source()->url();       # /images/source/01.jpg
echo $image->source()->path();      # /home/user/my-site.com/public/images/source/01.jpg
echo $image->source()->width();     # 3840 
echo $image->source()->height();    # 2160
echo $image->source()->ratio();     # 1.77777778

# Transformed image data:
echo $image->cached()->url();       # /images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.jpg
...

Use the toArray() method to export the image to a plain array:

$data = $image->toArray();

# [
#     "source" => [
#         "url" => "/images/source/01.jpg",
#         "path" => "/home/user/my-site.com/public/images/source/01.jpg",
#         "width" => 3840,
#         "height" => 2160,
#         "ratio" => 1.77777778,
#     ],
#     "cached" => [
#         "url" => "/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.jpg",
#         ...
#     ],
# ]

Image resampling

Image resampling is a simple way to reuse a transformed image as the source of another transformation. Use the resample() method to begin:

$greyscale = $flou->resample('01.jpg', [
    'filt' => 'greyscale',
    'w' => 2000,
]);

This is the same as calling image(), but returns an instance of ResampledImage instead. The resampled image can then be used again as a source for image():

$image = $flou->image($greyscale, ['w' => 50]);

# Source image:
echo $image->source()->url();       # /images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg
echo $image->source()->width();     # 2000
...

# Transformed image:
echo $image->cached()->url();       # /images/cache/_r/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg/9a5bdd58bbc27a556121925569af7b0c.jpg
echo $image->cached()->width();     # 50
...

Image Rendering

Rendering single images

The render() method on the image returns an ImageRender object, which prepares HTML suitable for the vanilla-lazyload library. Then, img() is used to render an img element:

$image = $flou->image('01.jpg');

echo $image
        ->render()
        ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
See HTML Output
<img 
  class="lazyload w-full" 
  alt="Lorem ipsum" 
  src="/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif" 
  data-src="/images/source/01.jpg" 
  width="3840" 
  height="2160"
>

Options passed into img() are included as HTML attributes on the element. Attribute values are not escaped by default.

Some attributes are automatically generated (e.g. src, width, height, etc.). You can override them with a ! prefix:

echo $image
        ->render()
        ->img([
            'class' => 'w-full',
            'alt' => 'Lorem ipsum',
            '!src' => false,
        ]);
See HTML Output
<img 
  class="lazyload w-full" 
  alt="Lorem ipsum" 
  data-src="/images/source/01.jpg" 
  width="3840" 
  height="2160" 
>

Render options

The ImageRender object can be configured to optimize the HTML output:

  • useAspectRatio(): Prevents content shifting when the LQIP is replaced with the source image:

    echo $image
            ->render()
            ->useAspectRatio()
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
    
    # or use a custom aspect-ratio:
    
    echo $image
            ->render()
            ->useAspectRatio(16 / 9)
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
    See HTML Output
    <img 
      class="lazyload w-full" 
      alt="Lorem ipsum" 
      style="aspect-ratio: 1.77777778; object-fit: cover; object-position: center;" 
      ...
    >

  • usePaddingTop(): A workaround for older browsers not supporting the aspect-ratio CSS property:

    echo $image
            ->render()
            ->usePaddingTop()
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
    
    # or use a custom aspect-ratio:
    
    echo $image
            ->render()
            ->usePaddingTop(16 / 9)
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
    See HTML Output
    <div class="lazyload-padding" style="position: relative; padding-top: 56.25%;">
      <img
        class="lazyload w-full"
        alt="Lorem ipsum" 
        style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; 
               object-fit: cover; object-position: center;"
        ...
      >
    </div>

  • useWrapper(): Wraps the image with an extra div and separates the LQIP element from the main img element. This is used to add a fade-in effect when the image is loaded.

    (Requires additional JS and CSS. See fade-in example.)

    echo $image
            ->render()
            ->useWrapper()
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
    See HTML Output
    <div class="lazyload-wrapper">
      <img 
        class="lazyload w-full" 
        alt="Lorem ipsum" 
        ...
      >
      <img 
        class="lazyload-lqip" 
        src="/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif"
      >
    </div>

  • useBase64Lqip(): Inlines a Base64 version of the LQIP in the src attribute of the img element. This reduces the number of HTTP requests needed to display a page, at the cost of making the HTML a bit heavier.

    echo $image
            ->render()
            ->useBase64Lqip()
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
    See HTML Output
    <img 
      class="lazyload w-full" 
      alt="Lorem ipsum" 
      src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH+EUNyZWF0Z..."
      data-src="/images/source/01.jpg" 
      width="2932" 
      height="2000"
    >

Default render options

You can set the default render options for all images in the ImageFactory configuration:

$flou = new ImageFactory([
    // ...
    'renderOptions' => [
        'aspectRatio' => true,
        'wrapper' => true,
        'base64Lqip' => true,
        // ...
    ],
]);
See Available Options
Name Type Description
baseClass string CSS class for img element. Default: 'lazyload'
wrapperClass string CSS class for wrapper element. Default: 'lazyload-wrapper'
lqipClass string CSS class for LQIP element. Default: 'lazyload-lqip'
paddingClass string CSS class for padding-specific wrapper element. Default: 'lazyload-padding'
aspectRatio boolean or number Use aspect ratio. Default: false
paddingTop boolean or number Use padding-top workaround. Default: false
wrapper boolean Use wrapper element. Default: false
base64Lqip boolean Use Base64 LQIP value. Default: false

Noscript variation

Use the noScript() method to render an img element without any lazy loading behavior:

echo $image
        ->render()
        ->noScript(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
See HTML Output
<img 
  class="lazyload-noscript w-full" 
  alt="Lorem ipsum" 
  src="/images/source/01.jpg" 
  width="2932" 
  height="2000"
>

This is used to add a noscript image fallback. (See Noscript image fallback example)

It can also be used creatively to work on the source image with CSS classes and HTML attributes. (See Browser native lazy loading example)

Image Sets (Responsive Images)

Single source (img element)

Use the imageSet() method to transform a source image into a set of responsive images:

$imageSet = $flou->imageSet([
    'image' => '01.jpg',
    'sizes' => '(max-width: 500px) 100vw, 50vw',
    'widths' => [500, 900, 1300, 1700],
]);

This returns an ImageSet object, which prepares all variations of the source image. The render() method on the image set returns an ImageSetRender instance, as seen before with single images:

echo $imageSet
        ->render()
        ->useAspectRatio()
        ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
See HTML Output
<img 
  class="lazyload w-full" 
  alt="Lorem ipsum" 
  style="aspect-ratio: 1.77777778; object-fit: cover; object-position: center;" 
  src="/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif" 
  data-src="/images/cache/01.jpg/b8648e93b40b56d5c5a78acc7a23e3d9.jpg" 
  data-srcset="/images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 500w, 
               /images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg 900w, 
               /images/cache/01.jpg/1eac615f1a50f20c434e5944225bdd4f.jpg 1300w, 
               /images/cache/01.jpg/b8648e93b40b56d5c5a78acc7a23e3d9.jpg 1700w" 
  data-sizes="(max-width: 500px) 100vw, 50vw"
  width="1700" 
  height="956" 
>

Like ImageRender, you can optimize ImageSetRender with the same methods:

  • useAspectRatio()
  • usePaddingTop()
  • useWrapper()
  • useBase64Lqip()
  • noScript()

Multiple sources (picture element)

With a similar configuration, imageSet() also handles multiple source images:

$imageSet = $flou->imageSet([
    [
        'image' => 'portrait.jpg',
        'media' => '(max-width: 1023px)',
        'sizes' => '100vw',
        'widths' => [400, 800, 1200],
    ],
    [
        'image' => 'landscape.jpg',
        'media' => '(min-width: 1024px)',
        'sizes' => '66vw',
        'widths' => [800, 1200, 1600],
    ],
]);

Then, the picture() method is used to render a picture element:

echo $imageSet
        ->render()
        ->picture(['class' => 'my-image', 'alt' => 'Lorem ipsum']);
See HTML Output
<picture>
  <source
    media="(max-width: 1023px)"
    data-sizes="100vw"
    data-srcset="/images/cache/portrait.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 400w,
                 /images/cache/portrait.jpg/1422c06dea2257858f6437b9675fba1c.jpg 800w,
                 /images/cache/portrait.jpg/de828e8798017be816f79e131e41dcc9.jpg 1200w"
  >
  <source 
    media="(min-width: 1024px)" 
    data-sizes="66vw" 
    data-srcset="/images/cache/landscape.jpg/c6f9c52bea237b64cc98fc9f5f3f15c6.jpg 800w,
                 /images/cache/landscape.jpg/fcc882305b523e823c7a24df05045c5a.jpg 1200w,
                 /images/cache/landscape.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 1600w"
  >
  <img
    class="lazyload my-image"
    alt="Lorem ipsum"
    src="/images/cache/landscape.jpg/66d1d4a938d99f2b0234e08008af09a8.gif"
    data-src="/images/cache/landscape.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg"
    width="1600"
    height="900"
  >
</picture>

See also: Art-directed picture element example

Multiple formats (picture element)

The configuration allows multiple image formats for each source:

$imageSet = $flou->imageSet([
    'image' => '01.jpg',
    'sizes' => '100vw',
    'widths' => [400, 800, 1200, 1600],
    'formats' => ['webp', 'jpg'],
]);

echo $imageSet
        ->render()
        ->picture(['class' => 'my-image', 'alt' => 'Lorem ipsum']);
See HTML Output
<picture>
  <source
    type="image/webp"
    data-srcset="/images/cache/01.jpg/7c7086baa60bb4b3876b14dd577fa9e8.webp 400w,
                 /images/cache/01.jpg/49ada5db20e72d539b611e5d17640d2f.webp 800w,
                 /images/cache/01.jpg/cb48c273b44bd0f00155d4932231fe28.webp 1200w,
                 /images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.webp 1600w"
    data-sizes="100vw"
  >
  <source
    type="image/jpeg"
    data-srcset="/images/cache/01.jpg/27e8a3f7fb4abe60654117a34f2007e1.jpg 400w,
                 /images/cache/01.jpg/f319ea155d0009a7e842f50fcc020fe3.jpg 800w,
                 /images/cache/01.jpg/cfdad3b69ae3a15ba479aa85868e75f3.jpg 1200w,
                 /images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg 1600w"
    data-sizes="100vw"
  >
  <img
    class="lazyload w-full"
    alt="Lorem ipsum"
    src="/images/cache/01.jpg/bd0dc309cfc3b71731b2e2df3d6e130b.gif"
    data-src="/images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg"
    width="1600"
    height="900"
  >
</picture>

Custom Glide parameters

You can provide an array of base Glide parameters as a second argument to imageSet():

$imageSet = $flou->imageSet([
    'image' => '01.jpg',
    'sizes' => '100vw',
    'widths' => [400, 800, 1200, 1600],
    'formats' => ['webp', 'jpg'],
], [
    'q' => 80,
]);

You'll find all available parameters in the Glide documentation.

Note: You may use all parameters except w and fm, which are automatically generated from the widths and formats configuration above.

Remote Images

The base ImageFactory class is optimized for cases where both source and cached images exist on the local filesystem. The RemoteImageFactory class was introduced to enable new use-cases:

  • Working with remote Glide endpoints
  • Integrating with existing Glide Server configurations

This adds support for images stored on remote filesystems, such as Amazon S3.

Configuration

The options for RemoteImageFactory are:

Name Type Description
glideServer League\Glide\Server A Server instance.
glideUrlBase string Alternatively, the base URL for a remotely accessible Glide server.
glideUrlSignKey string Private key used for Glide HTTP signatures. (optional)

Glide Endpoint

If you already have a Glide instance setup and publicly accessible, you can hook into it with the following configuration:

$flou = new Pboivin\Flou\RemoteImageFactory([
    'glideUrlBase' => '/glide'// or use a full URL: https://cdn.my-site.com/glide
    'glideUrlSignKey' => 'secret',
]);

$image = $flou->image('test.jpg');

//...

Glide Server

Alternatively, you can pass in a fully configured Glide Server object:

// @see https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/

$sourceFilesystem = new League\Flysystem\Filesystem(
    new League\Flysystem\AwsS3V3\AwsS3V3Adapter(/* S3 adapter configuration */);
);

$server = League\Glide\ServerFactory::create([
    'source' => $sourceFilesystem,
    'cache' => '/home/my-site.com/storage/glide-cache',
    'base_url' => '/glide',
]);

$flou = new Pboivin\Flou\RemoteImageFactory([
    'glideServer' => $server,
    'glideUrlSignKey' => 'secret',
]);

$image = $flou->image('test.jpg');

//...

If you're using Laravel, you can access the filesystem driver from the Storage facade:

// @see https://laravel.com/docs/filesystem

$sourceFilesystem = Illuminate\Support\Facades\Storage::disk('s3')->getDriver(),

//...

Caveats

When using RemoteImageFactory, it is too costly to fetch remote images to analyze their dimensions. Therefore, rendered images will not include width and height attributes. I recommend leveraging useAspectRatio() with a fixed aspect ratio value if possible.

Similarly, useBase64Lqip() will return a blank placeholder instead of a Base64 encoded LQIP.

Image resampling is not available for remote images.

Examples

Fade-in image on load

Extra JS and CSS:

<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.min.js"></script>
<script>
    /**
     * vanilla-lazyload API reference: 
     * https://github.com/verlok/vanilla-lazyload#options
     */

    document.addEventListener("DOMContentLoaded", () => {
        new LazyLoad({
            elements_selector: ".lazyload",

            callback_loaded: (el) => {
                const wrapper = el.closest(".lazyload-wrapper");
                if (wrapper) {
                    wrapper.classList.add("loaded");
                }
            }
        });
    });
</script>

<style>
    /* Example styles — adjust to taste */

    .lazyload-wrapper {
        position: relative;
        overflow: hidden;
    }

    .lazyload-wrapper .lazyload-lqip {
        filter: blur(10px);
        transform: scale(1.1);
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
    }

    .lazyload-wrapper.loaded .lazyload-lqip {
        opacity: 0;
        transition: opacity 0.5s;
        transition-delay: 0.5s;
    }
</style>

Usage:

<?= $flou
        ->image('01.jpg')
        ->render()
        ->useAspectRatio()
        ->useWrapper()
        ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
?>

Art-directed picture element

CSS:

<style>
    .my-image {
        width: 100%;
        height: auto;
        aspect-ratio: calc(3 / 4);
        object-fit: cover;
        object-position: center;
    }

    @media screen and (min-width: 1024px) {
        .my-image {
            max-width: 66vw;
            aspect-ratio: calc(16 / 9);
        }
    }
</style>

Usage:

<?= $flou->imageSet([
        [
            'image' => 'portrait.jpg',
            'media' => '(max-width: 1023px)',
            'sizes' => '100vw',
            'widths' => [400, 800, 1200],
        ],
        [
            'image' => 'landscape.jpg',
            'media' => '(min-width: 1024px)',
            'sizes' => '66vw',
            'widths' => [800, 1200, 1600],
        ],
    ])
    ->render()
    ->picture(['class' => 'my-image', 'alt' => 'Lorem ipsum']);
?>

Noscript fallback

CSS:

<noscript>
    <style>
        .lazyload {
            display: none;
        }
    </style>
</noscript>

Usage:

<div>
    <?= ($image = $flou->image('01.jpg'))
            ->render()
            ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum'])
    ?>
    <noscript>
        <?= $image
                ->render()
                ->noScript(['class' => 'w-full', 'alt' => 'Lorem ipsum'])
        ?>
    </noscript>
</div>

Native lazy loading (no JS, with LQIP)

Usage:

<?= ($image = $flou->image('01.jpg'))
        ->render()
        ->useAspectRatio()
        ->noScript([
            'class' => 'w-full', 
            'alt' => 'Lorem ipsum',
            'loading' => 'lazy',
            'decoding' => 'async',
            'style' => "background-image: url({$image->cached()->url()});
                        background-size: cover;"
        ]);
?>

Lazy loaded background image

Usage:

<?php $image = $flou->image('01.jpg'); ?>

<div class="lazyload"
     data-bg="<?= $image->source()->url() ?>"
     style="background-image: url( <?= $image->cached()->url() ?> );
            background-size: cover;"
>
    <!-- ... -->
</div>

CLI script

Preprocess all images in a source directory and prepare a JSON inventory file:

<?php

require 'vendor/autoload.php';

$flou = new Pboivin\Flou\ImageFactory([
    'sourcePath' => './public/images/source',
    'cachePath' => './public/images/cache',
    'sourceUrlBase' => '/images/source',
    'cacheUrlBase' => '/images/cache',
]);

$data = [];

foreach (glob('./public/images/source/*.jpg') as $path) {
    $file = basename($path);

    echo "Processing image: $file\n";

    $data[$file] = [
        'source' => $flou->image($file)->source()->toArray(),
        'lqip' => $flou->image($file)->cached()->toArray(),
        'responsive' => array_map(
            fn ($width) => $flou->image($file, ['w' => $width])->cached()->toArray(),
            [500, 900, 1300, 1700]
        ),
    ];
}

file_put_contents('./data/images.json', json_encode($data, JSON_PRETTY_PRINT));

echo "Done!\n";

Development

Test suite (phpunit)

composer run test

Static analysis (phpstan)

composer run analyse

Code formatting (pint)

composer run format

License

Flou is open-sourced software licensed under the MIT license.

pboivin/flou 适用场景与选型建议

pboivin/flou 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 198 次下载、GitHub Stars 达 13, 最近一次更新时间为 2022 年 07 月 28 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「php」 「images」 「lazy」 「loading」 「glide」 「lqip」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 pboivin/flou 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 pboivin/flou 我们能提供哪些服务?
定制开发 / 二次开发

基于 pboivin/flou 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2022-07-28