<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>回响笔记 - 文章分类</title><link href="/" rel="alternate"/><link href="/feeds/wen-zhang-fen-lei.atom.xml" rel="self"/><id>/</id><updated>2025-09-07T10:00:00+02:00</updated><entry><title>S3 多文件下载优化：从本地打包到实时流式 ZIP 传输</title><link href="/s3-duo-wen-jian-xia-zai-you-hua-cong-ben-di-da-bao-dao-shi-shi-liu-shi-zip-chuan-shu.html" rel="alternate"/><published>2025-09-07T10:00:00+02:00</published><updated>2025-09-07T10:00:00+02:00</updated><author><name>作者名</name></author><id>tag:None,2025-09-07:/s3-duo-wen-jian-xia-zai-you-hua-cong-ben-di-da-bao-dao-shi-shi-liu-shi-zip-chuan-shu.html</id><summary type="html">&lt;h1&gt;S3 多文件下载优化：从本地打包到实时流式 ZIP 传输&lt;/h1&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;记录一次 S3 多文件下载解决方法的优化。希望对碰到类似需求的 …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;S3 多文件下载优化：从本地打包到实时流式 ZIP 传输&lt;/h1&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;记录一次 S3 多文件下载解决方法的优化。希望对碰到类似需求的伙计有所帮助。&lt;/p&gt;
&lt;h1&gt;优化前方案&lt;/h1&gt;
&lt;p&gt;我们先看看优化前的做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务端从 S3 中下载所有目标文件，保存到本地打包成一个压缩文件&lt;/li&gt;
&lt;li&gt;服务端将处理好的压缩文件上传 S3，将对应的 S3 分享链接返回给客户端&lt;/li&gt;
&lt;li&gt;客户端通过分享链接，直接从 S3 下载压缩文件&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="流程图1" src="/static/CbxYbJcYDo79yWx4cM7ciowpn5f.png"&gt;&lt;/p&gt;
&lt;p&gt;从以上的流程图中可以发现，整个过程来回的下载上传下载，效率很低。特别是大文件的处理非常耗时，只能以异步的方式处理用户的请求，这样会影响用户的体验；并且这种方案会导致 S3 存储冗余。&lt;/p&gt;
&lt;p&gt;所以发现这些问题后，决定优化一下。&lt;/p&gt;
&lt;h1&gt;优化过程&lt;/h1&gt;
&lt;p&gt;开始考虑，S3 是否有接口可以让多文件的直接打包，由 S3 自身完成，就可以减少下载和上传带宽占用，速度肯定也更快。&lt;/p&gt;
&lt;p&gt;查看 minio（开源的 S3 项目）文档后，发现并没有多文件打包的 api 接口，只有个 &lt;a href="https://docs.min.io/community/minio-object-store/developers/python/API.html#compose_object"&gt;compose_object&lt;/a&gt; 的接口，是实现分片上传后的文件整合，但这个接口有硬性要求，除了最后一个文件其他文件必须大于 5M。因为这个限制，一些场景就无法使用，所以 pass。&lt;/p&gt;
&lt;p&gt;最终选择了&lt;strong&gt;流量中转方案&lt;/strong&gt;，以&lt;strong&gt;流式传输&lt;/strong&gt;的方式（数据&lt;strong&gt;边生成/读取边传输&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;服务端作为 S3 和客户端的中转，一边从 S3 下载文件，一边以流式的方式往客户端传输。&lt;/p&gt;
&lt;p&gt;&lt;img alt="流程图2" src="/static/Ui8MbCtHkoDNbixn2uQcZs7wnxd.png"&gt;&lt;/p&gt;
&lt;p&gt;流量中转的方案需采用流式响应，内存占用更小，实时性更好，不必等到全部数据准备好再发送，非常适合处理大文件。&lt;/p&gt;
&lt;h1&gt;Python 实时流式 ZIP 处理解决方案&lt;/h1&gt;
&lt;p&gt;为了实现 S3 多文件中转下载，直接返回文件肯定不行，需要将 S3 的多文件加工成 zip 文件。涉及流式 zip 处理。因为项目是 django 框架，而 Python 原生的 &lt;code&gt;zipfile&lt;/code&gt; 库无法进行真正的流式 ZIP 处理。&lt;/p&gt;
&lt;p&gt;可以使用临时文件方式先将所有 s3 文件全部下载到磁盘，然后再返回给客户端。&lt;/p&gt;
&lt;p&gt;更好的方式是使用实时流式 zip 传输，边下载 S3 文件，边以 zip 格式传输下载好的内容。减少响应等待时间。&lt;/p&gt;
&lt;p&gt;为了实现低内存占用的实时流式 ZIP 生成，找到以下两种第三方库：&lt;/p&gt;
&lt;h2&gt;1. &lt;strong&gt;zipfly&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;项目地址&lt;/strong&gt;: &lt;a href="https://github.com/sandes/zipfly"&gt;https://github.com/sandes/zipfly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;项目始于 2020 年 3 月，自 2020 年 7 月以来低维护，但整体稳定。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;只能使用磁盘路径作为输入，无法直接处理内存缓冲区或流。&lt;/li&gt;
&lt;li&gt;基本是通过替换流写入器实现持续数据刷新到磁盘的“黑客式”方案。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;评估&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虽然知名，但限制较多，不推荐用于高效内存管理场景。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. &lt;strong&gt;stream-zip&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;项目文档&lt;/strong&gt;: https://stream-zip.docs.trade.gov.uk/get-started/（因为不知名原因 github 仓库地址被移除）&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2021 年 12 月创建，并持续积极维护。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;对 ZIP 标准有深厚技术知识，作者用纯 Python 实现了 Deflate64 编码/解码器。&lt;/li&gt;
&lt;li&gt;支持使用 zlib 原生 C 代码直接生成压缩块。&lt;/li&gt;
&lt;li&gt;API 允许从流中实时生成输入数据，可处理文件描述符，实现内存高效的实时数据读取。&lt;/li&gt;
&lt;li&gt;可针对每个文件调整压缩级别。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存占用极低：例如创建 8GB ZIP 文件时，Python 仅占用约 6MB 内存。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;高性能：支持以 128KB 块进行输入/输出流式传输。&lt;/li&gt;
&lt;li&gt;与原生 &lt;code&gt;zipfile&lt;/code&gt; 相比，性能和内存效率提升显著。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推荐用途&lt;/strong&gt;: 高效生成大文件 ZIP、实时流式数据压缩。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩展：&lt;/strong&gt;如果还需要 &lt;strong&gt;流式解压缩&lt;/strong&gt;，同一作者提供的相关项目也是最佳选择，代码安全、符合 ZIP 标准、维护良好。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;核心代码&lt;/h2&gt;
&lt;p&gt;对比下来选择使用性能更好，持续维护的 stream-zip 包来实现，核心代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;stream_zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stream_zip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ZIP_64&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generate_streaming_zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;buffer_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
    &lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_s3_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;file_iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;返回一个生成器，按块读取 S3 对象内容&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Body&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer_size&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ClientError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error downloading &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from S3: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;

    &lt;span class="c1"&gt;# 当前时间作为文件时间戳&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# 文件名&lt;/span&gt;
            &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;# 时间戳&lt;/span&gt;
            &lt;span class="mo"&gt;0o600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# 权限&lt;/span&gt;
            &lt;span class="n"&gt;ZIP_64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# 压缩方式&lt;/span&gt;
            &lt;span class="n"&gt;file_iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bucket_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;object_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stream_zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;说明：使用 &lt;code&gt;yield&lt;/code&gt; 流式返回 zip 文件内容，可以和 &lt;code&gt;StreamingHttpResponse&lt;/code&gt; 配合。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;其他知识点&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;为了让浏览器能够自动接管下载过程（浏览器接管下载进程。即使页面刷新，也不会中断下载进程），多文件下载中转接口采用了 GET 请求（浏览器无法“自动接管” POST 请求的下载过程）&lt;/li&gt;
&lt;li&gt;为了支持传递复杂参数，由后端生成一个临时下载 URL，包含查询下载任务 id（可关联查询到下载参数），增加随机 code 做为校验。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用临时文件通过磁盘读写方式来保存大文件内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;临时文件关闭后会被自动清理，不用担心磁盘残留问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;这里使用磁盘替换内存存储并不影响整体速度，因为网络是瓶颈，而磁盘比网络快得多&lt;/li&gt;
&lt;/ul&gt;</content><category term="文章分类"/><category term="标签1"/><category term="标签2"/></entry><entry><title>无广告、零上传：纯本地运行的在线图片转换网站</title><link href="/wu-yan-gao-ling-shang-chuan-chun-ben-di-yun-xing-de-zai-xian-tu-pian-zhuan-huan-wang-zhan.html" rel="alternate"/><published>2025-09-07T10:00:00+02:00</published><updated>2025-09-07T10:00:00+02:00</updated><author><name>非鱼</name></author><id>tag:None,2025-09-07:/wu-yan-gao-ling-shang-chuan-chun-ben-di-yun-xing-de-zai-xian-tu-pian-zhuan-huan-wang-zhan.html</id><summary type="html">&lt;h1&gt;无广告、零上传：纯本地运行的在线图片转换网站&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近找图片时碰到一个 &lt;strong&gt;AVIF 格式&lt;/strong&gt; 的，想快速转成 PNG，就想着去找在线转 …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;无广告、零上传：纯本地运行的在线图片转换网站&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近找图片时碰到一个 &lt;strong&gt;AVIF 格式&lt;/strong&gt; 的，想快速转成 PNG，就想着去找在线转换网站，因为它们不用下载安装，最方便呀。&lt;/p&gt;
&lt;p&gt;一开始用百度搜，结果跳出来的全是广告，基本没法用（吐槽百度 +1）；换到 Google 后找到了一个在线网站（aconvert），但体验依旧不佳——转换速度很慢，还夹带各种弹窗广告。&lt;/p&gt;
&lt;p&gt;后来发现了一个更好的开源的基于浏览器的图片转换网站。它最大的特点是 &lt;strong&gt;直接在浏览器本地运行，所有处理都在用户设备上完成&lt;/strong&gt;，不需要把文件上传到服务器。相比常见的在线工具（先上传、再处理、再下载），不仅避免了隐私泄露的担忧，还不用忍受铺天盖地的广告。&lt;/p&gt;
&lt;p&gt;此外，这个工具在不同尺寸屏幕上也有良好的适配，用起来方便实用。建议收藏！&lt;/p&gt;
&lt;h2&gt;项目介绍&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="static/VRCubxlHAoWAeKxlKbzcqVi3nag.png"&gt;&lt;/p&gt;
&lt;p&gt;一个运行在浏览器中的图片格式转换工具，无需登录，没有广告。&lt;/p&gt;
&lt;h2&gt;特点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;多种图片格式转换&lt;/strong&gt;：支持多种格式间互转，支持将常见格式转换为 JPG, PNG, GIF 和 WEBP 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对比与下载&lt;/strong&gt;：输出图片可与原图对比大小，支持一键打包 ZIP 下载。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;批量处理稳定&lt;/strong&gt;：大批量文件转换不卡顿，内存占用稳定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;纯浏览器运行&lt;/strong&gt;：无需服务器、无数据收集，可离线使用（支持 PWA 缓存）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;界面友好&lt;/strong&gt;：提供右键菜单操作与明暗主题，使用体验流畅自然。&lt;/p&gt;
&lt;h2&gt;已知问题&lt;/h2&gt;
&lt;p&gt;浏览器兼容性问题：请留意进入页面后弹出的浏览器兼容性提示（若有）。如下图是各种浏览器兼容情况（红色代表不可用，绿色代表可用），可以看出版本较新的 Chrome、Edge 和 UC、百度浏览器都支持。&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="static/MRK8bQhR1oHEyKxUVbYcl1XVnsd.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;移动端浏览器不支持文件夹选择（ iOS 似乎支持拖入文件夹），选择文件夹的控件未隐藏；&lt;/li&gt;
&lt;li&gt;对于尺寸非常巨大的图片会出现内存泄露错误并冻结处理引擎。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;使用方式&lt;/h2&gt;
&lt;p&gt;登录网页直接使用： &lt;a href="https://img.mowl.cc/"&gt;https://img.mowl.cc/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;有技术基础的可以去 github 项目，下载源码自己部署：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/MossTheFox/owl-image-tools"&gt;https://github.com/MossTheFox/owl-image-tools&lt;/a&gt;&lt;/p&gt;</content><category term="文章分类"/><category term="标签2"/></entry></feed>