QingCloud 文档 – 最佳实践 - kaiyun官方网站下载手机网页 /storage/object-storage/beat-practices/ Recent content in 最佳实践 on QingCloud 文档 Hugo -- gohugo.io Storage: 数据迁移方案 /storage/object-storage/beat-practices/migrate/ Fri, 28 Feb 2020 10:08:56 +0900 /storage/object-storage/beat-practices/migrate/ <p>QingStor 对象存储推出了无缝数据迁移方案,帮助用户将业务数据从自建平台或者其他对象存储平台高效完整地迁移至 QingStor 对象存储服务。</p> <p>本方案提供两种迁移方式:</p> <ul> <li>被动触发迁移</li> <li>用户主动迁移</li> </ul> <p>综合运用两种迁移方式,可以在不中断业务的前提下,平滑完整地进行迁移。本方案首先介绍 QingStor 对象存储提供的两种迁移方式,再对迁移步骤进行详细说明。</p> <h2 id="被动触发迁移---外部镜像">被动触发迁移 - 外部镜像</h2> <p>对于已经设置了外部镜像的 QingStor 对象存储 的 Bucket,当请求的 Object 在 Bucket 中不存在时,QingStor 对象存储服务端会把 Object Key 拼接在外部镜像源站后作为抓取的源链接,然后自动从源站抓取(回源),并写入到 Bucket 当中。在回源过程中,请求这个 Object 的客户端,有可能会下载到源站文件,也有可能收到重定向到源站相应路径的 302 请求。在回源完成后,客户端能够直接从 QingStor 对象存储的 Bucket 中获取这个 Object。QingStor 对象存储建议用户可先使用 <a href="/storage/object-storage/api/object/basic_opt/head/">Head Object</a> 返回 200 成功来确认 Object 存在。</p> <p><img src="bucket_external_mirror_diagram.png" alt=""></p> <p>下面结合示意图,说明外部镜像的工作原理:</p> <p>假设用户的外部镜像源站为 <code>http://img.example.com</code>,在 QingStor 对象存储的 Bucket 的默认域名为 <code>http://bucketname.pek3a.qingstor.com</code>。</p> <p><strong>示意图左侧为首次请求:</strong></p> <ol> <li>用户发起获取 Object 的请求,如 <code>http://bucketname.pek3a.qingstor.com/blog.png</code>;</li> <li>Object 在 QingStor 对象存储的 Bucket 中不存在,且用户已经为该 Bucket 设置了外部镜像源站,此时,QingStor 对象存储服务端会将 Object Key <code>blog.png</code> 拼接到源站,生成源链接 <code>http://img.example.com/blog.png</code>;</li> <li>QingStor 对象存储服务端从该源链接抓取;</li> <li>在抓取过程中,请求该 Object 的客户端,有可能会下载到源站文件,也有可能收到重定向到源站相应路径的 302 请求。</li> </ol> <p><strong>示意图右侧为抓取完成后,再次发起请求:</strong></p> <ol> <li>用户发起获取 Object 的请求;</li> <li>Object 在 QingStor 对象存储的 Bucket 中已存在,直接返回。</li> </ol> <p><strong>备注:</strong></p> <ul> <li>外部镜像 API 请参考 <a href="/storage/object-storage/api/bucket/external_mirror/">Bucket External Mirror</a></li> <li>外部镜像,需要源站在提供下载文件时能返回 <code>Content-Length</code> 头,否则回源失败。</li> </ul> <h2 id="用户主动迁移---fetch-api">用户主动迁移 - Fetch API</h2> <p>如果需要迁移单个源站资源,可以使用 <a href="/storage/object-storage/api/object/basic_opt/fetch/">PUT Object - Fetch</a> 接口。该接口通过请求头 <code>x-qs-fetch-source</code> 附带源链接。QingStor 对象存储会从该链接抓取资源,保存到指定的 Object 中, 并且在抓取时能够自动处理源链接服务器返回的 301/302/307 等重定向请求。该接口同步下载文件,完成后才会返回结果。</p> <p>使用该接口,根据不同场景,QingStor 对象存储服务端会返回如下错误信息:</p> <ul> <li>409 :fetch_in_process,表明同一时间相同源链接的 Fetch 请求正在进行,或者被动触发的外部镜像功能正在抓取该源链接对应的文件;</li> <li>404 :object_not_exists,获取的 Object 不存在;</li> <li>503 :upstream_failed,无法与 Fetch 请求的源链接或者外部镜像的源站建立链接,或源站服务器返回 200,3xx,404 范围之外的错误代码,或链接中断;</li> <li>其他错误码,可参考:<a href="/storage/object-storage/api/error_code/">错误信息</a></li> </ul> <p>当 Object 特别大或源站下载速度比较慢时,该 API 请求有可能会导致客户端 TCP 超时。针对这种情形,QingStor 对象存储建议用户可以先使用 <a href="/storage/object-storage/api/object/basic_opt/head/">Head Object</a> 获取并比较源站文件和 Bucket 中 Object 的大小与时间戳,如果 Head Object 返回 404,客户端需重复调用一次 Fetch Object 接口; 请求若返回 409 则说明抓取已经开始。</p> <p><strong>备注:</strong></p> <ul> <li>Fetch API 请参考 <a href="/storage/object-storage/api/object/basic_opt/fetch/">PUT Object - Fetch</a>;</li> <li>Fetch 功能,需要源站在提供下载文件时能返回 <code>Content-Length</code> 头,否则回源失败;</li> <li>QingStor 对象存储建议用户使用本文介绍的 <a href="#%E7%94%A8%E6%88%B7%E4%B8%BB%E5%8A%A8%E8%BF%81%E7%A7%BB---qscamel">qscamel</a> 工具,便捷的实现迁移。</li> </ul> <h2 id="用户主动迁移---qscamel">用户主动迁移 - qscamel</h2> <p>qscamel 是把 HTTP(s) 形式的数据高效地批量迁移到 QingStor 对象存储的命令行工具。其输入可以是包含源链接的文件,也可以是其他对象存储平台的 Bucket 名称。更多介绍请参考 <a href="/storage/object-storage/manual/tool/qscamel/">qscamel</a></p> <p><strong>qscamel 有如下特点:</strong></p> <ol> <li>支持并行迁移,即同时迁移多个对象。</li> <li>支持给迁移任务命名,用来继续迁移未完成的迁移任务。qscamel 会记录本次迁移任务中成功迁移的源站。在退出后、重新执行时,qscamel 会跳过已经成功迁移的源站资源,迁移剩下未完成的源站资源。</li> <li>支持灵活的覆盖模式。qscamel 默认进行增量迁移,即通过比较源站资源和 QingStor 对象存储的 Bucket 中 Object 的最后修改时间,仅同步 QingStor 对象存储的 Bucket 中已存在但非最新的 Object,及 QingStor 对象存储的 Bucket 中不存在的 Object。</li> <li>除了默认的增量迁移外,qscamel 还支持参数 <code>--ignore-existing</code>,即:不覆盖 QingStor 对象存储的 Bucket 中已存在的 Object;以及参数 <code>--overwrite</code> ,强制覆盖 QingStor 对象存储的 Bucket 中已存在的 Object。</li> <li>支持指定日志文件。qscamel 默认输出到标准输出,也可以指定输出到日志文件。</li> </ol> <h2 id="推荐使用步骤">推荐使用步骤</h2> <ol> <li>通过命令行工具 qscamel 将冷数据批量迁移到 QingStor 对象存储。</li> <li>更改业务的数据上传路径,将路径设置为 QingStor 对象存储的 Bucket 默认域名,或者 QingStor 对象存储的 Bucket 所绑定的自定义域名。</li> <li>配置外部镜像功能,以使得访问不存在的 Object 时触发回源。</li> <li>外部镜像功能只针对 Object 不存在的情况,不会从源站拉取新版本 Object。如果业务逻辑中没有覆盖更新同一 Object 的场景,可以直接让业务从 QingStor 对象存储中读取;如果业务逻辑中有覆盖更新同一 Object 的逻辑,建议先更新业务代码,让业务上传数据时同时更新到源站和 QingStor 对象存储,然后就可以使用 QingStor 对象存储来承载业务。</li> <li>再次使用命令行工具 qscamel 增量同步,以保证源站到 QingStor 对象存储的 Bucket 数据迁移完成,没有遗漏。</li> <li>停止源站的使用。</li> </ol> <h2 id="faq">FAQ</h2> <ol> <li> <p>请问回源过程下载数据会产生费用吗?</p> <p>会按照请求次数收费。内网下载请求不收流量费。假如是外网下载请求,若已经回源成功到 QingStor 对象存储的 Bucket 中,会收取下载流量费用;假如返回 302 重定向到源站,则不收取流量费用。</p> </li> <li> <p>请问被动迁移有什么限制 ?</p> <p>源站方面需要返回文件的长度 <code>Content-Length</code>,并且从源站下载不能要求用户认证。QingStor 对象存储会最大限度保证客户端请求成功。</p> </li> </ol> Storage: 实时增量数据备份解决方案 /storage/object-storage/beat-practices/backup/ Fri, 28 Feb 2020 10:08:56 +0900 /storage/object-storage/beat-practices/backup/ <p>实时且增量地对数据进行备份是用户的普遍需求,本方案将描述如何在 Linux 下将本地业务数据实时备份至 QingStor 对象存储的 Bucket 中。</p> <p>本方案采用 lsyncd 实时地监控指定目录在文件系统层次的变化,当该目录下发生文件创建、重命名、删除等操作时,<a href="/storage/object-storage/manual/tool/qsctl/">qsctl</a> 便会被自动调用以将数据同步至指定的 QingSor 对象存储的 Bucket 中。因为 qsctl 支持增量同步,所以整个方案可以做到实时且增量的数据备份。</p> <p>下面介绍完整的配置步骤。</p> <h2 id="环境要求">环境要求</h2> <p>本方案仅支持 Linux Kernel 2.6.13 及其以后的版本,且要求 Kernel 编译选项 <code>CONFIG_INOTIFY_USER</code> 为开启状态。用户可以使用如下命令来确认自己的系统是否支持该方案:</p> <p><strong>本地终端输入如下命令行:</strong></p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>grep INOTIFY_USER /boot/config-<span style="color:#66d9ef">$(</span>uname -r<span style="color:#66d9ef">)</span> </span></span></code></pre></div><p><strong>返回结果:</strong></p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>CONFIG_INOTIFY_USER<span style="color:#f92672">=</span>y </span></span></code></pre></div><p>则说明可以采用该方案。</p> <h2 id="软件配置">软件配置</h2> <ol> <li> <p>安装 lsyncd,使用系统对应的包管理器进行安装即可</p> <p>Debian、Ubuntu 等系统:</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install lsyncd </span></span></code></pre></div><p>CentOS、Fedora 等系统:</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo yum install epel-release <span style="color:#f92672">&amp;&amp;</span> yum makecache </span></span><span style="display:flex;"><span>sudo yum install lsyncd </span></span></code></pre></div></li> <li> <p>使用 pip 安装并配置 qsctl,qsctl 的详细配置可以参考 <a href="/storage/object-storage/manual/tool/qsctl/">qsctl 的文档</a></p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install qsctl -U </span></span></code></pre></div></li> </ol> <h2 id="使用方法">使用方法</h2> <p>假设需要同步的目录为 <code>/tmp/example</code>, 待同步的 QingStor 对象存储的 Bucket 为 <code>example-bucket</code>,lsyncd 的配置文件为 <code>~/qingstor-backup.conf</code></p> <h3 id="编辑配置文件">编辑配置文件</h3> <p><strong>lsyncd 配置文件</strong></p> <pre tabindex="0"><code class="language-plain_text" data-lang="plain_text">settings { logfile = &#34;/tmp/lsyncd.log&#34;, statusFile = &#34;/tmp/lsyncd.status&#34;, maxDelays = 10, } bash = { delay = 5, maxProcesses = 1, onCreate = &#34;/path/to/qsctl sync ^source ^target --delete&#34;, onModify = &#34;/path/to/qsctl sync ^source ^target&#34;, onDelete = &#34;/path/to/qsctl sync ^source ^target --delete&#34;, onMove = &#34;/path/to/qsctl sync ^source ^target --delete&#34;, onStartup = &#34;/path/to/qsctl sync ^source ^target --delete&#34;, } sync { bash, source = &#34;/tmp/example&#34;, target = &#34;qs://example-bucket&#34; } </code></pre><p><strong>qsctl 配置文件</strong></p> <p>若不知道 qsctl 的完整路径,可以使用 <code>which qsctl</code> 以获取。配置文件中,各配置项含义说明如下:</p> <ul> <li><code>logfile:</code> 日志文件配置。</li> <li><code>statusFile:</code> 状态文件配置。</li> <li><code>maxDelays:</code> 当延时的事件到达此限制后将会直接运行,即使还没有到达延时的事件,单位为秒。</li> <li><code>delay:</code> 延时设置,事件触发后将会等待对应时间再运行,单位为秒。</li> <li><code>maxProcesses:</code> 最大进程数配置,最多允许同时运行的进程数。</li> </ul> <h3 id="测试配置">测试配置</h3> <ol> <li> <p>由于 lsyncd 没有默认的配置文件,运行 lsyncd 时,需在命令行中通过参数的形式给出。</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ lsyncd -nodaemon ~/qingstor-backup.conf </span></span></code></pre></div></li> <li> <p>创建一个文件并等待五秒左右</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ echo <span style="color:#e6db74">&#34;Hello, QingStor!&#34;</span> &gt; /tmp/example/ </span></span></code></pre></div></li> <li> <p>查看 “hello.txt” 是否在 Bucket “example-bucket” 中存在</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ qsctl ls qs://example-bucket/hello.txt </span></span></code></pre></div></li> <li> <p>若存在,则表示配置成功,否则表示配置存在问题。</p> </li> </ol> <h3 id="后台运行-lsyncd">后台运行 lsyncd</h3> <ol> <li> <p>当测试配置没有问题,且测试时运行结果符合预期时,可以将 lsyncd 放入后台运行</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ lsyncd ~/qingstor-backup.conf </span></span></code></pre></div></li> <li> <p>查看配置文件中指定的状态文件以了解 lsyncd 的运行状态</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat /tmp/lsyncd.status </span></span><span style="display:flex;"><span>Lsyncd status report at Mon Jan <span style="color:#ae81ff">16</span> 02:31:48 <span style="color:#ae81ff">2017</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>Sync1 source<span style="color:#f92672">=</span>/tmp/incron/ </span></span><span style="display:flex;"><span>There are <span style="color:#ae81ff">0</span> delays </span></span><span style="display:flex;"><span>Excluding: </span></span><span style="display:flex;"><span>nothing. </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>Inotify watching <span style="color:#ae81ff">2</span> directories </span></span><span style="display:flex;"><span>1: /tmp/incron/ </span></span><span style="display:flex;"><span>2: /tmp/incron/test/ </span></span></code></pre></div></li> <li> <p>查看配置文件中指定的日志文件地址以查看 lsyncd 的运行日志</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat /tmp/lsyncd.log </span></span><span style="display:flex;"><span>Mon Jan <span style="color:#ae81ff">16</span> 02:10:49 <span style="color:#ae81ff">2017</span> Normal: Event Init spawns action <span style="color:#e6db74">&#34;/home/xuanwo/.pyenv/shims/qsctl&#34;</span> </span></span><span style="display:flex;"><span>Mon Jan <span style="color:#ae81ff">16</span> 02:10:51 <span style="color:#ae81ff">2017</span> Error: Failure on startup of <span style="color:#e6db74">&#34;/tmp/incron/&#34;</span>. </span></span><span style="display:flex;"><span>...... </span></span><span style="display:flex;"><span>Mon Jan <span style="color:#ae81ff">16</span> 02:31:38 <span style="color:#ae81ff">2017</span> Normal: Startup of <span style="color:#e6db74">&#34;/tmp/incron/&#34;</span> finished. </span></span><span style="display:flex;"><span>Mon Jan <span style="color:#ae81ff">16</span> 02:33:46 <span style="color:#ae81ff">2017</span> Normal: --- TERM signal, fading --- </span></span></code></pre></div></li> </ol> Storage: 移动 App 接入方案 /storage/object-storage/beat-practices/app_integration/ Fri, 28 Feb 2020 10:08:56 +0900 /storage/object-storage/beat-practices/app_integration/ <p>伴随着移动互联网的浪潮,移动应用 App 的开发需求越来越多样化,无论何种功能都离不开文件存储。在以前,应用开发者需要自行开发构建文件服务器,用于保存数据文件,并面向互联网提供访问,例如社交网络中的用户照片,视频网站中的影音内容,电商平台中的商品图片。</p> <p>然而这些数据通常由终端用户直接生产和消费,伴随着用户增长,服务器的压力也会日益增大,开发者很难构建一个兼具高可用性、高扩展性和低成本的文件服务器。QingStor 对象存储刚好填补了这方面的需求,可为移动开发者提供一站式的文件存储解决方案。</p> <p>QingStor 对象存储提供 HTTP RESTful 风格的访问接口,并辅助于编程语言相关的 SDK 工具,帮助开发者用极少的开发成本,将移动应用 App 对接到 QingStor 对象存储。本文将介绍三种不同使用场景下,与 QingStor 对象存储对接的方案:</p> <ul> <li>使用 QingStor 对象存储 SDK</li> <li>开发者实现签名服务器</li> <li>表单 POST 上传</li> </ul> <p>最后再介绍一种特殊的签名认证方式:请求参数签名,用于当 Bucket 为私有权限时,在用户之间分享 Bucket 中的文件链接。</p> <h2 id="准备工作">准备工作</h2> <h3 id="创建-bucket">创建 Bucket</h3> <p>使用 QingStor 对象存储之前,用户需创建相应的 Bucket,用于储存 Object。详细操作可参考 <a href="/storage/object-storage/manual/console/bucket_manage/basic_opt/#%E5%88%9B%E5%BB%BA-bucket">创建 Bucket</a>。</p> <h3 id="创建-access-key">创建 Access Key</h3> <p>详细操作可参考 <a href="/storage/object-storage/api/practices/signature/#%E8%8E%B7%E5%8F%96-access-key">创建 Access Key</a>。</p> <h2 id="使用-qingstor-sdk">使用 QingStor SDK</h2> <p>QingStor 对象存储目前已提供多种语言的 SDK,包括开发 iPhone App 使用的 Swift 语言,以及开发 Android App 使用的 Java 语言。App 开发者将 SDK 嵌入到客户端程序中,通过调用 SDK 提供的方法和接口与 QingStor 对象存储服务端通信。</p> <p>这里将以开发一个上传照片的 iPhone App 为例,讲解如何使用 QingStor 对象存储的 SDK for Swift 将 App 接入到 QingStor 对象存储。</p> <h3 id="配置-cocoapods">配置 CocoaPods</h3> <ol> <li>如果你还没有安装 CocoaPods,可以执行以下命令来安装,版本需为:CocoaPods 1.1.0+</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gem install cocoapods </span></span></code></pre></div><ol start="2"> <li>在项目工程根目录下,输入以下命令来初始化 CocoaPods</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pod init </span></span></code></pre></div><ol start="3"> <li>打开 Podfile,增加 <code>pod 'QingStorSDK'</code> 至对应的 Target,内容参考如下:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>target <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#66d9ef">do</span> </span></span><span style="display:flex;"><span> use_frameworks! </span></span><span style="display:flex;"><span> pod <span style="color:#e6db74">&#39;QingStorSDK&#39;</span> </span></span><span style="display:flex;"><span>end </span></span></code></pre></div><ol start="4"> <li>最后,输入以下命令来安装 QingStor 对象存储 SDK 的依赖包</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pod install </span></span></code></pre></div><h3 id="开始编码">开始编码</h3> <p>本文目的是学习 QingStor 对象存储的 SDK,而不是花费时间来研究 UI,关于例子中用到的页面大概讲解如下:</p> <p><strong>Main.storyboard UI结构图:</strong></p> <p><img src="main_storyboard.png" alt=""></p> <p>使用一个 BucketListController 显示当前的 Bucket 列表,一个 ObjectListController 显示 Bucket 下面的对象列表。</p> <ol> <li>创建一个 Config.plist,并添加到工程。在 plist 文件里面填写申请的 API 密钥信息,内容参考如下</li> </ol> <pre tabindex="0"><code class="language-plist" data-lang="plist">access_key_id ACCESS_KEY_ID secret_access_key SECRET_ACCESS_KEY </code></pre><ol start="2"> <li>打开工程的 AppDelegate.swift 文件,在文件的顶部添加如下代码,导入 SDK:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">QingStorSDK</span> </span></span></code></pre></div><ol start="3"> <li>在程序的启动入口,添加全局的注册操作,代码如下:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">application</span>(<span style="color:#66d9ef">_</span> application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -&gt; Bool { </span></span><span style="display:flex;"><span> <span style="color:#75715e">// Register QingStorSDK</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> url = URL(fileURLWithPath: Bundle.main.path(forResource: <span style="color:#e6db74">&#34;Config&#34;</span>, ofType: <span style="color:#e6db74">&#34;plist&#34;</span>)<span style="color:#f92672">!</span>) </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">try</span>! Registry.registerFrom(plist: url) </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span> </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><ol start="4"> <li>打开 BucketListController,在 viewDidLoad 方法里面初始化我们的 QingStor 对象存储服务:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span>fileprivate <span style="color:#66d9ef">var</span> qsService: QingStor! </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#66d9ef">override</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">viewDidLoad</span>() { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">super</span>.viewDidLoad() </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> qsService = QingStor() </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> setupView() </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><ol start="5"> <li>为了灵活使用,除了上述初始化 QingStor 对象存储服务的方法之外,SDK 也支持在服务初始化的时候传入配置信息:</li> </ol> <ul> <li>首先修改 Config.plist 内容如下:</li> </ul> <pre tabindex="0"><code class="language-plist" data-lang="plist"> access_key_id ACCESS_KEY_ID secret_access_key SECRET_ACCESS_KEY protocol https host qingstor.com port 443 </code></pre><ul> <li>修改 qsService 的初始化方式:</li> </ul> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> url = URL(fileURLWithPath: Bundle.main.path(forResource: <span style="color:#e6db74">&#34;Config&#34;</span>, ofType: <span style="color:#e6db74">&#34;plist&#34;</span>)<span style="color:#f92672">!</span>) </span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> context = <span style="color:#66d9ef">try</span>! APIContext(plist: url) </span></span><span style="display:flex;"><span>qsService = QingStor(context: context) </span></span></code></pre></div><ol start="6"> <li>有了 qsService 实例,我们可以用它来获取 Bucket 列表,请求操作代码如下,并把得到的响应数据存储到 <code>listBucketsOutput</code>:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">private</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">requestBucketList</span>() { </span></span><span style="display:flex;"><span> qsService.listBuckets(input: ListBucketsInput()) { response, error <span style="color:#66d9ef">in</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> response = response { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> response.output.errMessage == <span style="color:#66d9ef">nil</span> { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.listBucketsOutput = response.output </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.tableView.reloadData() </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>response.output.errMessage<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>error<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.refreshControl?.endRefreshing() </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><ol start="7"> <li>刷新 TableView 的时候根据输出显示对应的内容,最终效果如下图所示:</li> </ol> <p><img src="bucket_list.png" alt=""></p> <p><strong>说明:</strong></p> <ul> <li><code>mobile-bucket-test</code> 是之前为了测试创建的 Bucket。</li> <li>点击该列表项,会跳转至 <strong>ObjectListController</strong> 页面,并把当前列表项的 Bucket 数据传递过去。</li> </ul> <ol start="8"> <li>打开 <strong>ObjectListController</strong> 文件,在监听 <code>bucketModel</code> 的事件里,初始化 Bucket 服务:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span>fileprivate <span style="color:#66d9ef">var</span> bucketService: Bucket! </span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> bucketModel: BucketModel! { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">didSet</span> { </span></span><span style="display:flex;"><span> title = bucketModel.name </span></span><span style="display:flex;"><span> bucketService = QingStor().bucket(bucketName: bucketModel.name!, zone: bucketModel.location!) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><ol start="9"> <li>增加 <strong>requestObjectList</strong> 方法来请求 Object 列表,并把得到的响应数据存储到 <code>listObjectsOutput</code>:</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">private</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">requestObjectList</span>() { </span></span><span style="display:flex;"><span> bucketService.listObjects(input: ListObjectsInput()) { response, error <span style="color:#66d9ef">in</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> response = response { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> response.output.errMessage == <span style="color:#66d9ef">nil</span> { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.listObjectsOutput = response.output </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.tableView.reloadData() </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>response.output.errMessage<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>error<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.refreshControl?.endRefreshing() </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><ol start="10"> <li>刷新 TableView 的时候根据输出显示对应的内容,最终效果如下图所示:</li> </ol> <p><img src="object_list.png" alt=""></p> <p><strong>说明:</strong></p> <ul> <li>点击右上角 <code>+</code> 符号,会触发选择图片事件</li> <li>使用 UIImagePickerController 来做图片选择器</li> </ul> <ol start="11"> <li>当选择完图片时会触发 UIImagePickerController 的回调方法,可以在该方法做一些图片上传的处理,代码如下</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">imagePickerController</span>(<span style="color:#66d9ef">_</span> picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { </span></span><span style="display:flex;"><span> dismiss(animated: <span style="color:#66d9ef">true</span>, completion: <span style="color:#66d9ef">nil</span>) </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> contentType: String? = <span style="color:#66d9ef">nil</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> UTI = info[UIImagePickerControllerMediaType], <span style="color:#66d9ef">let</span> type = UTTypeCopyPreferredTagWithClass(UTI <span style="color:#66d9ef">as</span>! CFString, kUTTagClassMIMEType)?.takeRetainedValue() { </span></span><span style="display:flex;"><span> contentType = type <span style="color:#66d9ef">as</span> String </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> contentType = <span style="color:#e6db74">&#34;image/jpeg&#34;</span> </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> pathExtension = <span style="color:#e6db74">&#34;jpg&#34;</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> url = info[UIImagePickerControllerReferenceURL] <span style="color:#66d9ef">as</span>? URL { </span></span><span style="display:flex;"><span> pathExtension = url.pathExtension.lowercased() </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> image = info[UIImagePickerControllerEditedImage] <span style="color:#66d9ef">as</span>? UIImage { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> originRightBarButtonItem = navigationItem.rightBarButtonItem </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> spinner.startAnimating() </span></span><span style="display:flex;"><span> navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner) </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> data = UIImageJPEGRepresentation(image, <span style="color:#ae81ff">0.8</span>)<span style="color:#f92672">!</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> input = PutObjectInput(contentLength: data.count, contentType: contentType, bodyInputStream: InputStream(data: data)) </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> key = <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>Int<span style="color:#e6db74">(</span>Date<span style="color:#e6db74">()</span>.timeIntervalSince1970<span style="color:#e6db74">))</span><span style="color:#e6db74">.</span><span style="color:#e6db74">\(</span>pathExtension<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span> </span></span><span style="display:flex;"><span> bucketService.putObject(objectKey: key, input: input) { response, error <span style="color:#66d9ef">in</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> response = response { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> response.output.errMessage == <span style="color:#66d9ef">nil</span> { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.beginRefresh() </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>response.output.errMessage<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>error<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.navigationItem.rightBarButtonItem = originRightBarButtonItem </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><p>代码有点长,不过我们应该关注的地方只有下面这一段代码</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> data = UIImageJPEGRepresentation(image, <span style="color:#ae81ff">0.8</span>)<span style="color:#f92672">!</span> </span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> input = PutObjectInput(contentLength: data.count, contentType: contentType, bodyInputStream: InputStream(data: data)) </span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> key = <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>Int<span style="color:#e6db74">(</span>Date<span style="color:#e6db74">()</span>.timeIntervalSince1970<span style="color:#e6db74">))</span><span style="color:#e6db74">.</span><span style="color:#e6db74">\(</span>pathExtension<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span> </span></span><span style="display:flex;"><span>bucketService.putObject(objectKey: key, input: input) { response, error <span style="color:#66d9ef">in</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> response = response { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> response.output.errMessage == <span style="color:#66d9ef">nil</span> { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.beginRefresh() </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>response.output.errMessage<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> { </span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;error: </span><span style="color:#e6db74">\(</span>error<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>) </span></span><span style="display:flex;"><span> } </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">self</span>.navigationItem.rightBarButtonItem = originRightBarButtonItem </span></span><span style="display:flex;"><span>} </span></span></code></pre></div><p><strong>说明:</strong></p> <ul> <li>首先把图片信息转换成 Data,方便后续转成 InputStream,也可以将 Image 存到本地文件,再转成 InputStream。</li> <li>再把文件的一些信息传给 Input 对象,比如 ContentLength 和 ContentType。</li> <li>key 值是存储的文件名,用于后续的删除下载操作,这里只是简单的获取了一下时间戳当做文件名。</li> <li>最后用 bucketService 来把文件上传到 QingStor 对象存储,通过调用 putObject 方法来实现。</li> </ul> <p>这个简单的教程到这里就告一段落了,例子里面还加了一个侧滑删除的操作,大家有兴趣可以 <a href="https://github.com/qychrisyang/qingstor-sdk-swift-demo">下载源码</a> 看看。</p> <h2 id="开发者实现签名服务器">开发者实现签名服务器</h2> <p>上文中介绍的使用 SDK 对接 QingStor 对象存储服务,适用于 Bucket 被个人用户所拥有的情况。如果 Bucket 为 App 开发者所拥有,由于需要将签名密钥内置到客户端程序中,会带来安全方面的隐患。为了保证签名密钥的安全,开发者可以根据 QingStor 对象存储签名方法,自己搭建并实现一个签名服务器。用于签名的密钥只需要在服务端保存,客户端不需要拿到,从而避免了认证信息泄漏的隐患。</p> <p>QingStor 对象存储提供了一个签名服务器的 <a href="https://github.com/yunify/qingstor-demo-auth-server">样例</a> 供 App 开发者参考。网页端使用 JavaScript SDK 配合签名服务器进行上传可以参考官方提供的 <a href="https://github.com/yunify/qingstor-sdk-js-demo">Demo 项目</a>。</p> <h3 id="注意事项">注意事项</h3> <p>若用户需自己开发签名服务,需要注意:</p> <ul> <li> <p>签名服务需要考虑 <a href="/storage/object-storage/api/practices/signature/#%E9%92%88%E5%AF%B9-javascript-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%E7%89%B9%E6%AE%8A%E8%80%83%E8%99%91">JavaScript 客户端的特例</a></p> </li> <li> <p>签名服务需要把计算签名时所有的时间戳,返回给客户端,客户端根据这个时间戳设置 <code>Date</code> 头字段(一般客户端)或 <code>x-qs-date</code> 头字段(JavaScript客户端)</p> </li> <li> <p>由于没有权限验证,该样例不适合直接运行在生产环境,若暴露在公网上,任何人都可以访问并进行签名认证。</p> </li> </ul> <p>样例的使用步骤如下:</p> <h3 id="安装签名服务器">安装签名服务器</h3> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://github.com/yunify/qingstor-demo-auth-server.git </span></span><span style="display:flex;"><span>cd qingstor-demo-auth-server </span></span><span style="display:flex;"><span>pip install -r requirements </span></span></code></pre></div><h3 id="配置认证信息">配置认证信息</h3> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export ACCESS_KEY_ID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;ACCESS_KEY_ID_EXAMPLE&#34;</span> </span></span><span style="display:flex;"><span>export SECRET_ACCESS_KEY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;SECRET_ACCESS_KEY_EXAMPLE&#34;</span> </span></span><span style="display:flex;"><span>export ZONE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;pek3a&#34;</span> </span></span></code></pre></div><p>其中 ZONE 可以在服务器端进行配置,也可以在每一次请求中指定。如果都没有的话,服务器将会抛出异常。</p> <h3 id="运行认证服务器">运行认证服务器</h3> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python demo.py </span></span></code></pre></div><h3 id="获得请求签名">获得请求签名</h3> <ol> <li>从客户端发送签名请求</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> -d <span style="color:#e6db74">&#39;{&#34;method&#34;:&#34;GET&#34;, &#34;url&#34;:&#34;/&#34;, &#34;headers&#34;:{&#34;Date&#34;:&#34;Wed, 10 Dec 2014 17:20:31 GMT&#34;}}&#39;</span> 127.0.0.1:5000 </span></span></code></pre></div><ol start="2"> <li>客户端将会获得如下的返回结果</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>QS PLLZOBTTZXGBNOWUFHZZ:vIWg/qAxvXlcFRb9uzYmdIM9tiF6EuM6SC3i13yLzH8<span style="color:#f92672">=</span> </span></span></code></pre></div><ol start="3"> <li>将该结果附加到请求头中,作为最终发送给 QingStor 对象存储的请求</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> /mybucket/music.mp3 <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> </span></span><span style="display:flex;"><span>Authorization<span style="color:#f92672">:</span> <span style="color:#ae81ff">QS PLLZOBTTZXGBNOWUFHZZ:vIWg/qAxvXlcFRb9uzYmdIM9tiF6EuM6SC3i13yLzH8=</span> </span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">pek3a.qingstor.com</span> </span></span><span style="display:flex;"><span>Date<span style="color:#f92672">:</span> <span style="color:#ae81ff">Mon, 14 Nov 2016 14:05:00 GMT</span> </span></span></code></pre></div><p>SDK 参考例子:<a href="/storage/object-storage/sdk/java/">Java 使用服务端签名</a></p> <h2 id="表单-post-上传">表单 POST 上传</h2> <p>表单上传是专门为浏览器设计的一种文件上传方式,表单上传分为以下几步:</p> <ol> <li>用户通过浏览器请求开发者的 Web Server</li> <li>Web Server 生成包含 <code>signature</code> 字段的表单返回给浏览器,例如</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span> Upload </span></span><span style="display:flex;"><span> .pek3a.qingstor.com&#34; method=&#34;POST&#34; enctype=&#34;multipart/form-data&#34;&gt; </span></span></code></pre></div><ol start="3"> <li>用户通过表单上传文件以及 Signature 给 QingStor 对象存储。表单上传的具体方法,请见 <a href="/storage/object-storage/api/object/post/">表单上传</a></li> </ol> <h2 id="请求参数签名">请求参数签名</h2> <p>最后我们介绍一种不同于请求头签名的认证方式,该方法适用于 Bucket 为私有权限,但是需要分享文件下载链接给其它用户,或者其它设置请求头签名不方便的客户端。如果开发者想要生成一个 QingStor 对象存储的访问链接,并将链接分享给其他用户,可以使用该签名方式,即请求参数签名。下面是一个请求示例</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> /music.mp3?access_key_id=PLLZOBTTZXGBNOWUFHZZ&amp;expires=1479107162&amp;signature=tuXu/KcggHWPAfEmraUHDwEUdiIPSXVRsO%2BT2rxomBQ%3D <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> </span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">mybucket.pek3a.qingstor.com</span> </span></span><span style="display:flex;"><span>Date<span style="color:#f92672">:</span> <span style="color:#ae81ff">Mon, 14 Nov 2016 14:05:00 GMT</span> </span></span></code></pre></div><p>参数签名使用的场合较为广泛,它所能达成的效果跟请求头签名完全一样,只是让附带签名的过程更加方便和易于使用。具体方法请见 <a href="/storage/object-storage/api/practices/signature">签名验证</a></p> Storage: Linux 使用对象存储作为本地盘 /storage/object-storage/beat-practices/linux_uses_object_storage_as_a_local_disk/ Mon, 01 Jan 0001 00:00:00 +0000 /storage/object-storage/beat-practices/linux_uses_object_storage_as_a_local_disk/ <p>本文介绍如何通过 goofys 将 QingStor 对象存储挂载到文件系统使用。项目地址:<a href="https://github.com/kahing/goofys">Github 项目</a>。</p> <h2 id="操作步骤">操作步骤</h2> <ol> <li> <p>创建 Bucket,若已经有可使用的 Bucket,可略过该步骤。详细操作可参考 <a href="/storage/object-storage/manual/console/bucket_manage/basic_opt/#%E5%88%9B%E5%BB%BA-bucket">创建 Bucket</a>。</p> </li> <li> <p>申请 API 密钥。详细操作可参考 <a href="/storage/object-storage/api/practices/signature/#%E8%8E%B7%E5%8F%96-access-key">创建 Access Key</a>。</p> </li> <li> <p>安装 goofys (以 Ubuntu 为例)</p> </li> </ol> <pre tabindex="0"><code>mkdir /usr/local/goofys cd /usr/local/goofys wget https://github.com/kahing/goofys/releases/latest/download/goofys chmod +x goofys echo &#34;PATH=\$PATH:/usr/local/goofys&#34; &gt;&gt; /etc/profile source /etc/profile </code></pre><ol start="4"> <li>挂载 Bucket</li> </ol> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd ~ </span></span><span style="display:flex;"><span>vim .aws/credentials </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>default<span style="color:#f92672">]</span> </span></span><span style="display:flex;"><span><span style="color:#75715e">#API密钥ID</span> </span></span><span style="display:flex;"><span>aws_access_key_id <span style="color:#f92672">=</span> qy_access_key_id </span></span><span style="display:flex;"><span><span style="color:#75715e">#API密钥密码</span> </span></span><span style="display:flex;"><span>aws_secret_access_key <span style="color:#f92672">=</span> qy_secret_access_key </span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>goofys --endpoint https://s3.&lt;zone&gt;.qingstor.com &lt;bucket_name&gt; &lt;mount_point&gt; </span></span></code></pre></div><ol start="5"> <li><code>df -Th</code> 查看结果</li> </ol> <p><img src="/storage/object-storage/_images/linux_uses_object_storage_as_a_local_disk.png" alt="image-20210523151936393"></p> Storage: 静态网站托管方案 /storage/object-storage/beat-practices/web_hosting/ Fri, 28 Feb 2020 10:08:56 +0900 /storage/object-storage/beat-practices/web_hosting/ <p>静态网站托管(Bucket Web Hosting),是 QingStor 对象存储,面向静态网页内容(包含音频和视频等文件)提供的一种托管服务,是对静态网站的文件存储、数据访问权限、 CDN 分发加速,以及安全保障等一站式的解决方案。网页内容包含音频和视频等文件。可帮助用户快速构建并托管基于静态内容的网站,并提供高可靠与高可用的服务保障,大幅简化用户建站的操作流程,同时大幅降低网站的日常运营与维护成本。</p> <p><img src="bucket_web_hosting.png" alt=""></p> <h2 id="主要优势">主要优势</h2> <h3 id="高可靠高可用">高可靠、高可用</h3> <ul> <li>继承 QingStor 对象存储高可靠高可用特性。</li> <li>存储容量可无限水平扩展。</li> <li>提供完善的 API 接口与管理功能。</li> <li>用户可便捷地上传与管理托管的静态内容。</li> </ul> <h3 id="操作简便">操作简便</h3> <ul> <li>配合 QingStor 对象存储提供的 <a href="/storage/object-storage/manual/tool/qsctl">qsctl</a> 文件上传工具,可实现对托管内容的快速实时更新,保障用户对网站内容的快速更新。</li> <li>用户可以通过 QingStor 对象存储完善的功能,实现对所托管网站的访问权限控制、流量监控、安全防护等功能。</li> </ul> <h3 id="无缝衔接-cdn-加速及-https-证书">无缝衔接 CDN 加速及 HTTPS 证书</h3> <ul> <li>整合了开·云app官方版下载 QingCloud CDN 加速服务,并支持对网站开启 HTTPS 证书认证,以满足用户对网站访问的更高需求。</li> <li>在控制台与静态网站托管功能进行无缝衔接,使用户在一致的操作体验中完成操作。</li> </ul> <h3 id="使用成本大幅降低">使用成本大幅降低</h3> <p>QingStor 对象存储成本低廉,无需采购、部署和运维,可按量付费。并且支持用户通过存储分层及对象生命周期管理,将成本降至更低。</p> <h2 id="应用场景">应用场景</h2> <h3 id="静态网站托管">静态网站托管</h3> <p>QingStor 对象存储静态网站托管可为企业官网、新闻站点、自媒体站点、个人博客及文档汇编等大量使用静态内容的网站提供一套使用简便、成本低廉、高效可靠的面向静态网页的托管方案。</p> <p>三步完成网站上线,省去繁杂的基础设施运维:</p> <ol> <li>将所有静态网站文件上传至 QingStor 对象存储 Bucket,并点选相应的配置;</li> <li>设置网站访问域名,选择是否开启 CDN 或 HTTPS 证书(证书需用户自行提前准备好);</li> <li>开启静态网站托管。</li> </ol> <p><strong>静态网站托管使用流程:</strong></p> <ol> <li>设置 Bucket 为公开可读:</li> </ol> <p><img src="/storage/object-storage/_images/web_hosting1.png" alt=""></p> <ol start="2"> <li>将网站内容上传至 Bucket。</li> </ol> <p><img src="bucket_web_hosting_example_4.gif" alt=""></p> <ol start="3"> <li>推荐使用 QingStor 对象存储提供的命令行工具 qsctl 进行上传,使用方法可以参考 <a href="/storage/object-storage/manual/tool/qsctl">qsctl 文档</a>。如使用 sync 命令上传整个网站,操作命令行为:</li> </ol> <pre tabindex="0"><code class="language-plain_text" data-lang="plain_text">qsctl sync /path/to/website qs://test-bucket </code></pre><ol start="4"> <li> <p>绑定自定义域名并开启静态网站设置,请参考 <a href="/storage/object-storage/beat-practices/web_hosting">控制台指南</a> 并操作。</p> </li> <li> <p>在浏览器中访问刚才绑定的自定义域名,即可查看网站内容。</p> </li> </ol> <h3 id="大型动态网站动静分离">大型动态网站动静分离</h3> <p>将静态内容单独拆分出来进行托管,是目前普遍的网站优化手段,QingStor 对象存储静态网站托管可帮助提升网站的整体运行效率,并降低使用成本。</p> <ul> <li>降低 Web 服务器负载,静态文件访问负载全部通过 CDN</li> <li>通过 QingStor 对象存储可与 QingCloud 大数据平台无缝集成,高效进行网站各项数据分析应用</li> <li>Web 服务可直接将海量非结构化数据存储至对象存储,降低存储成本</li> <li>配合 CDN 使用,降低流量成本,提高静态内容响应速度</li> <li>无限水平扩展的海量存储空间,无需考虑存储架构升级</li> </ul> <p><strong>Web 服务静态化拆分样例</strong></p> <ul> <li>原有 Web 服务架构</li> </ul> <p><img src="bucket_web_hosting_static_1.png" alt=""></p> <p>例如,有如上图的 Web 服务架构。客户端和浏览器通过统一的 Web 服务网关访问网页、多媒体文件、软件安装包、API 等,同时 API Server 还依赖数据库(如 MySQL)、缓存(如 Redis)、大数据平台(如 Hadoop、Spark 等)等其他后端服务。</p> <p>这种服务架构的缺点是所有访问请求都经由网关,如果 Web 服务的静态内容的访问量大,则会影响到 API Server 请求的响应速度。</p> <ul> <li>优化后 Web 服务架构</li> </ul> <p><img src="bucket_web_hosting_static_2.png" alt=""></p> <p>优化过的 Web 服务架构如上图。引入 QingStor 对象存储之后,网页、多媒体文件、软件包等静态文件存储至 Bucket。Bucket 可以开启静态网站托管服务,同时也可以使用 CDN 来提高访问速度。客户端或浏览器只有访问 API 时才会请求 Web 服务网关,能够去除静态文件的负载压力。 API Server 可将非结构化数据统一存储至 Bucket。Web 应用的数据也可以定时打包备份至 Bucket,大数据平台也能够接触对象存储实现计算与数据存储分离。</p> <p>这样一来,可以让 QingStor 对象存储支撑起整个 Web 服务的存储,无需再考虑存储容量和存储架构升级问题,同时也能降低使用云服务成本。</p> <h2 id="对比传统部署方式">对比传统部署方式</h2> <p><img src="bucket_web_hosting_traditional.png" alt=""></p> <p>网站的访问体验是站长以及开发者最关心的一点,通过上面小节的介绍,相比过去自主购买服务器并部署上线网站的标准流程(如上图),使用静态网站除了能够节省成本之外, 还帮助用户省去了面向服务器的所有繁杂的配置与部署操作,并且不需要考虑服务器的选型购买与维护更新。</p> <p><img src="bucket_web_hosting_improved.png" alt=""></p> <p>相比绝大多数类似服务,QingStor 对象存储提供的静态网站托管整合了 CDN 加速服务,并支持对网站开启 HTTPS 证书认证,对用户的访问效率和访问安全提供了保证,使静态网站的上线发布和更新维护流程变的简单高效。如上图所示。</p> Storage: Bucket Policy 白名单和黑名单使用实例 /storage/object-storage/beat-practices/policy/ Fri, 28 Feb 2020 10:08:56 +0900 /storage/object-storage/beat-practices/policy/ <p>ACL 提供了 Bucket 级别的读写权限设置,包括可读、可写和可读写。用户可以将不同的访问权限分别授予不同的开·云app官方版下载用户,也可以选择所有用户整体设置。Bucket Policy 允许用户更细粒度的控制 Bucket 的访问。其语义主要由用户、 资源、动作及条件运算符组合定义。例如仅允许指定用户以指定站点为 Referer 以下载 Bucket 中的某单个 Object,或者防止外链等。</p> <p>ACL 过于广域,对于一些更细粒度的控制无法实现,需要用 Policy 来实现数据访问权限的控制。</p> <p>以下是 Policy 规则分别使用白名单和黑名单实现相同结果的两个案例,从案例中熟悉 Policy 黑白名单的使用。最终实现的效果都是一样,即头字段里的 <code>referer</code> 必须匹配 <code>*.baidu.com</code>,才能允许访问。详细操作如下:</p> <h2 id="案例一acl无任何规则">案例一:ACL无任何规则</h2> <ol> <li>进入 <strong>访问控制</strong> 设置页面,清除所有 ACL 规则,详细操作可参考 <a href="/storage/object-storage/manual/console/bucket_manage/access_control/#%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6bucket-acl">Bucket ACL</a>:</li> </ol> <p><img src="/storage/object-storage/_images/policy1.png" alt=""></p> <ol start="2"> <li>进入 <strong>存储空间策略</strong> 设置页面,创建规则,设置 Referer 匹配 <code>*.baidu.com</code> 访问,也就是设置访问白名单。详细操作可参考 <a href="/storage/object-storage/manual/console/bucket_manage/access_control/#%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E7%AD%96%E7%95%A5bucket-policy">Bucket Policy</a>:</li> </ol> <p><img src="/storage/object-storage/_images/policy2.png" alt=""></p> <ol start="3"> <li>测试结果如下,直接 curl 访问提示无权限,添加 <code>--referer</code> 参数,指定 <code>www.baidu.com</code> 之后可以正常访问:</li> </ol> <p><img src="/storage/object-storage/_images/policy3.png" alt=""></p> <p>可见,ACL 不做任何规则限制,此时默认都无权访问,但是由于 Policy 优先级更高,可以直接越过 ACL 的限制。</p> <h2 id="案例二acl限制所有人可读">案例二:ACL限制所有人可读</h2> <ol> <li>进入 <strong>访问控制</strong> 设置页面,添加所有用户可读的 ACL 规则,详细操作可参考 <a href="/storage/object-storage/manual/console/bucket_manage/access_control/#%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6bucket-acl">Bucket ACL</a>:</li> </ol> <p><img src="/storage/object-storage/_images/policy4.png" alt=""></p> <ol start="2"> <li>进入 <strong>存储空间策略</strong> 设置页面,创建规则,设置 Referer 不匹配 <code>*.baidu.com</code> 访问,也就是添加访问黑名单。详细操作可参考 <a href="/storage/object-storage/manual/console/bucket_manage/access_control/#%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E7%AD%96%E7%95%A5bucket-policy">Bucket Policy</a>:</li> </ol> <p><img src="/storage/object-storage/_images/policy5.png" alt=""></p> <ol start="3"> <li>测试结果如下,直接 curl 访问提示无权限,添加 <code>--referer</code> 参数,指定 <code>www.baidu.com</code> 之后可以正常访问:</li> </ol> <p><img src="/storage/object-storage/_images/policy6.png" alt=""></p> <p>可见,实现的结果和 <strong>案例一</strong> 是一致的。</p>