<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Lary's Zone]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://lary.me/</link><generator>Ghost 0.11</generator><lastBuildDate>Thu, 09 Apr 2026 10:41:13 GMT</lastBuildDate><atom:link href="https://lary.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[CentOS 7中使用acme.sh为域名签署免费证书]]></title><description><![CDATA[<h6 id="">[写在前面]</h6>

<p><a href="https://github.com/Neilpang/acme.sh">acme.sh</a> 实现了<a href="https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment"> acme 协议</a>，可以从 <a href="https://letsencrypt.org/">let's encrypt</a> 生成免费的证书。 <br>
官方说明文档请参见：<a href="https://github.com/Neilpang/acme.sh/wiki">https://github.com/Neilpang/acme.sh/wiki</a>  </p>

<h6 id="1">1. 环境 &amp; 准备工作</h6>

<ul>
<li><p><strong>操作系统</strong> <br>
本篇博文以 <code>CentOS 7</code> 为例。  </p></li>
<li><p><strong>域名解析</strong>  </p>

<ul><li><p>说明 <br>
博主使用<a href="https://www.aliyun.com/">阿里云</a>提供的DNS解析，采用其他解析方式的童鞋请参照<a href="https://github.com/Neilpang/acme.sh/wiki">官方说明文档</a>进行操作。 <br>
acme.sh 采用 API 调用的方式自动添加/删除DNS解析，所以需要提供具有<code>管理云解析(DNS)的权限</code>的阿里云账号 AccessKey 与 AccessKeySecret。</p></li>
<li><p>如何获取</p></li></ul></li></ul>]]></description><link>https://lary.me/sign-certificates-with-acmesh/</link><guid isPermaLink="false">a57218b8-300b-4216-90a2-f4bf37227dd6</guid><category><![CDATA[Linux]]></category><category><![CDATA[acme.sh]]></category><category><![CDATA[Certificate]]></category><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Mon, 09 Jul 2018 07:47:11 GMT</pubDate><content:encoded><![CDATA[<h6 id="">[写在前面]</h6>

<p><a href="https://github.com/Neilpang/acme.sh">acme.sh</a> 实现了<a href="https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment"> acme 协议</a>，可以从 <a href="https://letsencrypt.org/">let's encrypt</a> 生成免费的证书。 <br>
官方说明文档请参见：<a href="https://github.com/Neilpang/acme.sh/wiki">https://github.com/Neilpang/acme.sh/wiki</a>  </p>

<h6 id="1">1. 环境 &amp; 准备工作</h6>

<ul>
<li><p><strong>操作系统</strong> <br>
本篇博文以 <code>CentOS 7</code> 为例。  </p></li>
<li><p><strong>域名解析</strong>  </p>

<ul><li><p>说明 <br>
博主使用<a href="https://www.aliyun.com/">阿里云</a>提供的DNS解析，采用其他解析方式的童鞋请参照<a href="https://github.com/Neilpang/acme.sh/wiki">官方说明文档</a>进行操作。 <br>
acme.sh 采用 API 调用的方式自动添加/删除DNS解析，所以需要提供具有<code>管理云解析(DNS)的权限</code>的阿里云账号 AccessKey 与 AccessKeySecret。</p></li>
<li><p>如何获取 AccessKey <br>
A. 进入阿里云控制台网页版，将鼠标悬停到头像上弹出下拉框，并在下拉框中选择<code>访问控制</code>（根据<a href="https://help.aliyun.com/document_detail/28642.html?spm=5176.2020520153.0.0.789c415dM4GQ74">阿里云安全最佳实践</a>，请避免直接使用具有完整管理权限的AccessKey，而改用具有特定权限(此处需要<code>管理云解析(DNS)的权限</code>)的RAM子用户AccessKey来进行API调用）。 <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/1.jpg" alt="image" title=""> <br>
B. 在访问控制面板中找到用户管理，新建用户（注意勾选<code>为该用户自动生成AccessKey</code>），并妥善保存AccessKey信息。 <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/2.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/3.jpg" alt="image" title=""> <br>
C. 为刚才新建的用户授予<code>管理云解析(DNS)的权限</code>。 <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/4.jpg" alt="image" title="">  </p></li></ul></li>
</ul>

<h6 id="2acmesh">2. 安装 acme.sh</h6>

<p>注：本篇博文以通过 git 的方式安装 acme.sh 为例进行说明，更多详细安装方法请参见 <a href="https://github.com/Neilpang/acme.sh/wiki/How-to-install">https://github.com/Neilpang/acme.sh/wiki/How-to-install</a>  </p>

<pre><code class="language-shell"># 安装git
$ sudo yum install -y git 

# 安装acme.sh到目录/opt/acme (安装目录可自行决定)
$ sudo git clone https://github.com/Neilpang/acme.sh.git /opt/acme
</code></pre>

<h6 id="3">3. 编写并执行生成证书的脚本文件</h6>

<ul>
<li><strong>编写脚本文件</strong> <br>
注： <br>
A. 编写脚本文件是为了方便后续为证书续期（签署一次证书有效期为90天，有了脚本文件，只需执行脚本就可以完成续期）。 <br>
B. 下方所签署的证书为ECC 256位证书，若签署RSA证书，可删除<code>--keylength ec-256 \</code>一行，默认签署RSA 2048位证书。  </li>
</ul>

<pre><code class="language- shell">#!/bin/sh

# acme.sh安装目录
export HOME=/opt/acme/  
# 阿里云AccessKey
export Ali_Key="your_access_key"  
# 阿里云AccessKeySecret
export Ali_Secret="your_access_key_secret"

# 为域名lary.me签署通配符证书，并将公私钥导出到/var/ssl/目录下
/opt/acme/acme.sh --issue \
    --dns dns_ali \
    -d *.lary.me -d lary.me \
    --fullchain-file /var/ssl/lary.me.crt \
    --key-file /var/ssl/lary.me.key \
    --keylength ec-256 \
    --debug \
    --force
</code></pre>

<ul>
<li><strong>执行脚本签署证书</strong> <br>
注：请确保脚本文件具有可执行权限（博主将脚本文件保存至 /home/lary/acme/acme-lary.me.sh）。    </li>
</ul>

<pre><code># 文件授权
$ sudo chmod +x /home/lary/acme/acme-lary.me.sh

# 执行脚本生成证书（执行脚本时会通过API验证域名解析，耗时120s）
$ sudo sh /home/lary/acme/acme-lary.me.sh
</code></pre>

<h6 id="4">4. 设置定时任务自动为证书续期</h6>

<ul>
<li><strong>使用 crontab 命令设置定时任务</strong> <br>
注：由于我选择将证书导出到 /var/ssl/ 目录下，需要管理员权限，所以使用 <code>sudo crontab -e</code> 命令（使用 root 的 crontab）设置定时任务。关于在 crontab 中使用 sudo ，请参见这篇回答：<a href="https://askubuntu.com/questions/173924/how-to-run-a-cron-job-using-the-sudo-command">https://askubuntu.com/questions/173924/how-to-run-a-cron-job-using-the-sudo-command</a></li>
</ul>

<pre><code class="language-shell"># 打开并修改root的crontab
$ sudo crontab -e

# 设置每月1号凌晨3点钟自动为证书续期
&gt; 0 3 1 * * /home/lary/acme/acme-lary.me.sh

# 设置crond服务自启
$ sudo systemctl enable crond

# 重启crond服务
$ sudo systemctl restart crond
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/5.jpg" alt="image" title=""> <br>
  <img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/6.jpg" alt="image" title=""> <br>
* 如果你的脚本文件中，设置将证书导出到无需 root 权限的目录（如用户目录），那可以参见下述方法设置定时任务：</p>

<pre><code class="language-shell"># 修改/etc/crontab以设置定时任务
$ sudo vi /etc/crontab

# 设置每月1号凌晨3点钟自动为证书续期（crontab简介请参见下方）
&gt; 0 3 1 * * lary /home/lary/acme/acme-lary.me.sh

# 设置crond服务自启
$ sudo systemctl enable crond

# 重启crond服务
$ sudo systemctl restart crond
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/7.jpg" alt="image" title=""> <br>
  <img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/8.jpg" alt="image" title="">  </p>

<ul>
<li><p><strong>crontab 命令简介</strong>  </p>

<ul><li><p>crontab命令用于设置周期性被执行的指令。该命令从标准输入设备读取指令，并将其存放于“crontab”文件中，以供之后读取和执行。  </p></li>
<li><p>每一个用户都可以有一个crontab文件来保存调度信息。系统管理员可以通过cron.deny 和 cron.allow 这两个文件来禁止或允许。  </p></li>
<li><p>通常，crontab储存的指令被守护进程激活，crond常常在后台运行，每一分钟检查是否有预定的作业需要执行，这类作业一般称为cron jobs。  </p></li></ul></li>
<li><p><strong>crontab 命令格式</strong>  </p></li>
</ul>

<pre><code class="language-shell"># .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed
</code></pre>

<ul>
<li><p><strong>crontab 命令特殊符号</strong>  </p>

<ul><li><p>星号（*）：代表所有可能的值，例如month字段如果是星号，则表示在满足其它字段的制约条件后每月都执行该命令操作。  </p></li>
<li><p>逗号（,）：可以用逗号隔开的值指定一个列表范围，例如，“1,2,5,7,8,9”。  </p></li>
<li><p>横杠（-）：可以用整数之间的中杠表示一个整数范围，例如“2-6”表示“2,3,4,5,6”。  </p></li>
<li><p>斜线（/）：可以用正斜线指定时间的间隔频率，例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用，例如*/10，如果用在minute字段，表示每十分钟执行一次。  </p></li></ul></li>
<li><p><strong>crontab 命令全局配置文件</strong>  </p>

<ul><li><p>cron.d <br>
<em>系统自动定期需要做的任务</em>  </p></li>
<li><p>cron.daily <br>
<em>每天执行一次的任务</em>  </p></li>
<li><p>cron.deny <br>
<em>用于控制不让哪些用户使用 crontab 的功能</em>  </p></li>
<li><p>cron.hourly <br>
<em>每小时执行一次的任务</em>  </p></li>
<li><p>cron.monthly <br>
<em>每月执行一次的任务</em>  </p></li>
<li><p>crontab <br>
<em>设定定时任务执行文件</em>  </p></li>
<li><p>cron.weekly <br>
<em>每星期执行一次的任务</em>  </p></li></ul></li>
</ul>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/sign-certificates-with-acmesh/9.jpg" alt="image" title="">  </p>]]></content:encoded></item><item><title><![CDATA[.NET Core和Docker的结合使用]]></title><description><![CDATA[<p>据说容器技术是我们这个行业的一个重要趋势，而博主恰好在近期遇到了这样的需求。</p>

<p><a href="https://blogs.msdn.microsoft.com/dotnet/2017/05/25/using-net-and-docker-together/">参考MSDN博客</a>，我们先来看看开发人员迁移到容器的关键原因：  </p>

<ul>
<li><p>一致性：容器包含应用程序及其所有依赖项。不管是在计算机、本地环境或是云端，应用程序都执行相同的代码。</p></li>
<li><p>轻量级：通过使用基于主机操作系统的最小量级抽象，并在容器之间共享公共资源，容器可以快速启动并实现最少的内存占用。</p></li>
<li><p>共享性：容器镜像可以通过 Docker Hub、Docker Store 和私有 Docker 仓库（如 Azure 镜像仓库）轻松实现分享。</p></li>
<li><p>简单而强大：DockerFile 格式（容器镜像的奥秘所在）是一种可以实现强大场景的简单格式：优雅地将操作系统和容器的特定命令结合，并且还可以创建 Docker 镜像层。</p></li>
</ul>

<p>接下来，我们看看具体如何在 Docker 中部署 .NET Core 应用。 <br>
注：本篇博客中操作系统使用 CentOS 7  </p>

<h6 id="1">1. 前期准备</h6>

<ul>
<li><p>.NET Core</p></li></ul>]]></description><link>https://lary.me/linux-net-core-docker/</link><guid isPermaLink="false">d7dc973f-cdf4-4651-a396-4708102abea5</guid><category><![CDATA[.Net Core]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Sun, 24 Jun 2018 01:33:11 GMT</pubDate><content:encoded><![CDATA[<p>据说容器技术是我们这个行业的一个重要趋势，而博主恰好在近期遇到了这样的需求。</p>

<p><a href="https://blogs.msdn.microsoft.com/dotnet/2017/05/25/using-net-and-docker-together/">参考MSDN博客</a>，我们先来看看开发人员迁移到容器的关键原因：  </p>

<ul>
<li><p>一致性：容器包含应用程序及其所有依赖项。不管是在计算机、本地环境或是云端，应用程序都执行相同的代码。</p></li>
<li><p>轻量级：通过使用基于主机操作系统的最小量级抽象，并在容器之间共享公共资源，容器可以快速启动并实现最少的内存占用。</p></li>
<li><p>共享性：容器镜像可以通过 Docker Hub、Docker Store 和私有 Docker 仓库（如 Azure 镜像仓库）轻松实现分享。</p></li>
<li><p>简单而强大：DockerFile 格式（容器镜像的奥秘所在）是一种可以实现强大场景的简单格式：优雅地将操作系统和容器的特定命令结合，并且还可以创建 Docker 镜像层。</p></li>
</ul>

<p>接下来，我们看看具体如何在 Docker 中部署 .NET Core 应用。 <br>
注：本篇博客中操作系统使用 CentOS 7  </p>

<h6 id="1">1. 前期准备</h6>

<ul>
<li><p>.NET Core 工程 <br>
本篇博客中新建一个 ASP.NET Core 工程作为示例 <br>
VS 2017 Version 15.7.4 <br>
.NET Core Runtime 2.1 <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/9.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/10.jpg" alt="image" title="">  </p></li>
<li><p>Linux 切换至 root 身份（方便后续所有需要相关权限的操作）</p></li>
</ul>

<pre><code class="language-shell">$ su -
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/1.jpg" alt="image" title="">  </p>

<h6 id="2docker">2. 安装 Docker</h6>

<p><a href="https://docs.docker.com/install/linux/docker-ce/centos/">详情请参考文档：<a href="https://docs.docker.com/install/linux/docker-ce/centos/">https://docs.docker.com/install/linux/docker-ce/centos/</a></a>  </p>

<pre><code class="language-shell">$ yum install -y docker

$ systemctl start docker
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/2.jpg" alt="image" title="">  </p>

<p>注： <br>
CentOS 7 安装好 Docker ，执行启动命令<code>systemctl start docker</code>时，可能出现如下报错： <br>
<code>Error starting daemon: SELinux is not supported with the overlay2 graph driver on this kernel. Either boot into a newer kernel or disable selinux in docker (--selinux-enabled=false)</code>  </p>

<p>重新编辑 Docker 配置文件即可(设置 OPTIONS 的<code>--selinux-enabled=false</code>或直接删除<code>--selinux-enabled</code>)：  </p>

<pre><code class="language-shell">$ vi /etc/sysconfig/docker
# /etc/sysconfig/docker

# Modify these options if you want to change the way the docker daemon runs

OPTIONS='--selinux-enabled=false  --log-driver=journald --signature-verification=false'  
if [ -z "${DOCKER_CERT_PATH}" ]; then  
    DOCKER_CERT_PATH=/etc/docker
fi

:wq

$ systemctl restart docker 
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/3.jpg" alt="image" title="">  </p>

<h6 id="3">3. 确认文件系统</h6>

<p>确保 /etc/sysconfig/docker-storage 中 DOCKER<em>STORAGE</em>OPTIONS="--storage-driver overlay"  </p>

<p>注：Docker默认只能识别overlay文件系统，若使用overlay2文件系统，运行Docker镜像时守护进程会报错： <br>
<code>Error response from daemon: error creating overlay mount to /var/lib/docker/overlay2/2c320fe7df5a0e659466c528063c640402e504bc4d1500b78d6a5142707b1487/merged: invalid argument.</code>  </p>

<pre><code class="language-shell">$ systemctl stop docker #停掉docker服务

$ rm -rf /var/lib/docker #特别注意：已拉取的docker镜像会被清空

$ vi /etc/sysconfig/docker-storage #设置DOCKER_STORAGE_OPTIONS="--storage-driver overlay"

$ vi /etc/sysconfig/docker #去掉option后面的--selinux-enabled，或将其值置为false(操作同安装Docker中的说明)

$ systemctl start docker #再次启动docker服务
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/4.jpg" alt="image" title="">  </p>

<h6 id="4etcdockerdaemonjson">4. 修改 /etc/docker/daemon.json 文件来配置镜像加速（考虑到网络防火长城）</h6>

<pre><code class="language-shell">$ vi /etc/docker/daemon.json
{
    "registry-mirrors": ["https://registry.docker-cn.com"]
}

:wq
</code></pre>

<p>注：请保证 /etc/docker/daemon.json 访问权限  </p>

<pre><code class="language-shell"># 授权单个文件
$ chmod 777 /etc/docker/daemon.json

# 递归授权整个文件夹
$ chmod -R 777 /var/lib/docker
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/5.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/6.jpg" alt="image" title="">  </p>

<h6 id="5microsoftdotnet">5. 拉取或导入microsoft/dotnet镜像</h6>

<p><strong>镜像类别：</strong>  </p>

<ul>
<li>microsoft/dotnet or microsoft/dotnet:latest (alias for the SDK image)  </li>
<li>microsoft/dotnet:sdk  </li>
<li>microsoft/dotnet:runtime  </li>
<li>microsoft/dotnet:runtime-deps  </li>
</ul>

<p><a href="https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images">详情请参见：<a href="https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images">https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images</a></a>  </p>

<p><strong>拉取镜像(以镜像 microsoft/dotnet:2.1-aspnetcore-runtime 为例)：</strong>  </p>

<pre><code class="language-shell">$ docker pull microsoft/dotnet:2.1-aspnetcore-runtime
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/7.jpg" alt="image"></p>

<p><code>*注：*</code> <br>
<code>*A. dotnet sdk的镜像包括了runtime与 dotnet 命令(CLI，即Command Line Interface)，体积较大(1.72GB)，而我们的程序想要正常运行，使用runtime的镜像(255MB)就足够了*</code> <br>
<code>*B. 本篇博客将以 asp.net core 的工程为例进行后续演示，且写作博文时 dotnet core runtime 版本为2.1，故使用镜像 microsoft/dotnet:2.1-aspnetcore-runtime*</code> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/8.jpg" alt="image"></p>

<p><strong>导入导出镜像：</strong>  </p>

<pre><code class="language-shell">#导出镜像(注：625b44243fbe为需要导出的镜像的ID)
$ docker save 625b44243fbe &gt; /home/lary/Documents/dotnetimage.tar

#导入镜像
$ docker load &lt; /home/lary/dotnetimage.tar

#修改导入镜像的名称与标签(注：所导入的镜像名称、标签均为none，625b44243fbe为导入的镜像的ID)
$ docker tag 625b44243fbe microsoft/dotnet:2.1-aspnetcore-runtime
</code></pre>

<h6 id="6centos">6. 将程序发布内容传输至 CentOS</h6>

<ul>
<li>发布程序
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/11.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/12.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/13.jpg" alt="image" title="">  </li>
<li>文件传输至 CentOS <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/14.jpg" alt="image" title=""> <br>
注：我将发布后的文件统统扔到 /home/lary/Projects/Lary.Test.Apis 目录下，以便后续操作</li>
</ul>

<h6 id="7docker">7. 创建 Docker 镜像 [可选]</h6>

<p><strong>1) 编写适当的 Dockerfile</strong> <br>
A. Dockerfile 实例  </p>

<pre><code class="language-Dockerfile">FROM microsoft/dotnet:2.1-aspnetcore-runtime  
MAINTAINER Lary Mao &lt;dev@lary.me&gt;

LABEL build-date="2018-06-23"

ENV ASPNETCORE_URLS http://0.0.0.0:8023  
WORKDIR /app

COPY * /app/

#也可以通过CMD命令启动脚本文件，并在脚本文件中写入dotnet启动命令
ENTRYPOINT ["dotnet", "Lary.Test.Apis.dll"]  
</code></pre>

<p>注： <br>
a. 请注意 Dockerfile 文件名不能有误 <br>
b. 我选择将 Dockerfile 放置到程序目录下。各位请根据实际情况修改 Dockerfile 中所涉及到的文件路径（如 COPY 命令） <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/15.jpg" alt="image" title="">  </p>

<p>B. 详细说明 <br>
参考：<a href="https://www.cnblogs.com/sorex/p/6481407.html">https://www.cnblogs.com/sorex/p/6481407.html</a> <br>
DockerFile分为四部分组成：基础镜像信、维护者信息、镜像操作指令和容器启动时执行指令。  </p>

<ul>
<li><strong>FROM</strong> <br>
第一行必须指明基于的基础镜像 <br>
<code>例：FROM microsoft/dotnet:2.1-aspnetcore-runtime</code>  </li>
<li><strong>MAINTAINER</strong> <br>
指定维护者的信息 <br>
<code>例：MAINTAINER Lary Mao dev@lary.me</code></li>
<li><strong>RUN</strong> <br>
格式为<code>Run</code>或者<code>Run [“executable” ,”Param1”, “param2”]</code> <br>
前者在shell终端上运行，即/bin/sh -C，后者使用exec运行。例如：<code>RUN [“/bin/bash”, “-c”,”echo hello”]</code> <br>
每条run指令在当前基础镜像执行，并且提交新镜像。当命令比较长时，可以使用“/”换行</li>
<li><strong>CMD</strong> <br>
构建容器后调用，也就是在容器启动时才进行调用。 <br>
支持三种格式: <br>
<code>CMD [“executable” ,”Param1”, “param2”]</code>使用exec执行，推荐 <br>
<code>CMD command param1 param2</code>，在/bin/sh上执行 <br>
<code>CMD [“Param1”, “param2”]</code>提供给ENTRYPOINT做默认参数  </li>
<li><strong>EXPOSE</strong> <br>
告诉Docker服务端容器暴露的端口号，供互联系统使用。在启动Docker时，可以通过-P,主机会自动分配一个端口号转发到指定的端口。使用-P，则可以具体指定哪个本地端口映射过来 <br>
例：<code>EXPOSE 22 80 8023</code>  </li>
<li><strong>ENV</strong> <br>
指定一个环境变量，会被后续 RUN 指令使用，并在容器运行时保持  </li>
<li><strong>ADD</strong> <br>
该命令将指定的文件添加到容器中。其中，源可以是Dockerfile所在目录的一个相对路径；也可以是一个URL；还可以是一个tar文件（自动解压至目录）</li>
<li><strong>COPY</strong> <br>
将构建命令所在的主机本地的文件或目录（注意源路径使用相对目录），复制到镜像文件系统（不会自动解压）</li>
<li><strong>ENTRYPOINT</strong> <br>
配置容器，使其可执行化 <br>
每个Dockerfile中只能有一个 ENTRYPOINT ，当指定多个时，只有最后一个起效 <br>
例：<code>ENTRYPOINT [“executable”, “param1”, “param2”]</code> <br>
或：<code>ENTRYPOINT command param1 param2 （shell中执行）</code>  </li>
<li><strong>VOLUME</strong> <br>
用于指定持久化目录，在容器启动时用-v传递参数，例如-v ~/opt/data/mysql:/var/lib/mysql将本机的~/opt/data/mysql和容器内的/var/lib/mysql做持久化关联 <br>
容器启动时会加载，容器关闭后会回写 <br>
例：<code>VOLUME [“/data”]</code>  </li>
<li><strong>USER</strong> <br>
指定运行容器时的用户名或UID，后续的 RUN 也会使用指定用户 <br>
当服务不需要管理员权限时，可以通过该命令指定运行用户。并且可以在之前创建所需要的用户，例如：<code>RUN groupadd -r postgres &amp;&amp; useradd -r -g postgres postgres</code>。要临时获取管理员权限可以使用 gosu ，而不推荐 sudo <br>
例：<code>USER daemon</code>  </li>
<li><strong>WORKDIR</strong> <br>
为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录。 <br>
可以使用多个 WORKDIR 指令，后续命令如果参数是相对路径，则会基于之前命令指定的路径。例如: <br>
<code>WORKDIR /a</code> <br>
<code>WORKDIR b</code> <br>
<code>WORKDIR c</code> <br>
<code>RUN pwd</code> <br>
则最终路径为 <code>/a/b/c</code> <br>
例：<code>WORKDIR /path/to/workdir</code>  </li>
<li><strong>ONBUILD</strong> <br>
配置当所创建的镜像作为其它新创建镜像的基础镜像时，所执行的操作指令。 <br>
例如，Dockerfile使用如下的内容创建了镜像 image-A 。 <br>
<code>[…]</code> <br>
<code>ONBUILD ADD . /app/src</code> <br>
<code>ONBUILD RUN /usr/local/bin/python-build –dir /app/src</code> <br>
<code>[…]</code> <br>
如果基于A创建新的镜像时，新的Dockerfile中使用 FROM image-A 指定基础镜像时，会自动执行 ONBUILD 指令内容，等价于在后面添加了两条指令 <br>
例：<code>ONBUILD [INSTRUCTION]</code>  </li>
</ul>

<p><strong>2) 通过命令创建 Docker 镜像</strong>  </p>

<pre><code class="language-shell">#授权，防止Docker守护进程访问文件权限不够
$ chmod -R 777 /home/lary/Projects/Lary.Test.Apis

#进入到Dockerfile目录
$ cd /home/lary/Projects/Lary.Test.Apis

#创建Docker镜像
$ docker build -t lary/apistest:runtime .
</code></pre>

<p>注：lary/apistest 是我为新镜像起的名字，runtime 是新镜像的标签（容器名要求全小写）。此外，请额外注意不要遗漏命令行末尾的那个点。 <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/16.jpg" alt="image" title="">  </p>

<h6 id="8docker">8. 创建 Docker 容器</h6>

<p>通过<code>docker run</code> 命令创建新容器（本篇博客中使用端口 8022）  </p>

<p><strong>1) 若未按照步骤 7 创建 Docker 镜像</strong> <br>
在<code>docker run</code> 命令中写入所需的相关信息（如程序工作目录、端口映射等，详情请参见与此步骤同级的详细说明↓）  </p>

<pre><code class="language-shell">$ docker run -w /app -e ASPNETCORE_URLS="http://0.0.0.0:8022" --entrypoint "dotnet" --name dotnet-test -h dotnet-test -p 8022:8022 -v /home/lary/Projects/Lary.Test.Apis:/app --privileged=true -d docker.io/microsoft/dotnet:2.1-aspnetcore-runtime Lary.Test.Apis.dll
</code></pre>

<p>注：命令中 -w 用于指定程序工作目录（WORKDIR），-e 用于指定 环境变量（ENV）， --entrypoint用于指定程序入口，其参数传递必须放置在创建容器所基于的镜像名之后，使用空格进行分隔（Lary.Test.Apis.dll 作为参数传递给 donet 命令） <br>
参考：<a href="https://medium.com/@oprearocks/how-to-properly-override-the-entrypoint-using-docker-run-2e081e5feb9d">https://medium.com/@oprearocks/how-to-properly-override-the-entrypoint-using-docker-run-2e081e5feb9d</a>  </p>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/17.jpg" alt="image" title="">  </p>

<p><strong>2) 若已按照步骤 7 创建 Docker 镜像</strong> <br>
直接根据新创建的镜像创建容器即可</p>

<pre><code class="language-shell">docker run --name dotnet-test -h dotnet-test -p 8023:8022 --privileged=true -d lary/apistest:runtime  
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/18.jpg" alt="image" title="">  </p>

<p><strong>3) 详细说明</strong>  </p>

<ul>
<li><strong>-a stdin</strong> <br>
指定标准输入输出内容类型，可选 STDIN/STDOUT/STDERR 三项    </li>
<li><strong>-d</strong> <br>
后台运行容器，并返回容器ID  </li>
<li><strong>-e key="value"</strong> <br>
设置环境变量  </li>
<li><strong>-h "host"</strong> <br>
指定容器的hostname  </li>
<li><strong>-i</strong> <br>
以交互模式运行容器，通常与 -t 同时使用  </li>
<li><strong>-m</strong> <br>
设置容器使用内存最大值  </li>
<li><strong>-p</strong> <br>
端口映射，使用方法为 主机上的端口:容器内部的端口  </li>
<li><strong>-t</strong> <br>
为容器重新分配一个伪输入终端，通常与 -i 同时使用  </li>
<li><strong>-v</strong> <br>
挂载数据卷，使用方法为 数据卷 -v 容器目录 或 -v 本地目录:容器目录  </li>
<li><strong>--add-host</strong> <br>
为容器添加host与ip的映射关系  </li>
<li><strong>--cpuset="0-2" or --cpuset="0,1,2"</strong> <br>
绑定容器到指定CPU运行  </li>
<li><strong>--dns 8.8.8.8</strong> <br>
指定容器使用的DNS服务器，默认和宿主一致  </li>
<li><strong>--dns-search example.com</strong> <br>
指定容器DNS搜索域名，默认和宿主一致  </li>
<li><strong>--env-file=[]</strong> <br>
从指定文件读入环境变量  </li>
<li><strong>--expose=[]</strong> <br>
开放一个端口或一组端口  </li>
<li><strong>--link=[]</strong> <br>
添加链接到另一个容器  </li>
<li><strong>--name="nginx-lb"</strong> <br>
为容器指定一个名称  </li>
<li><strong>--net="bridge"</strong> <br>
指定容器的网络连接类型，支持 bridge/host/none/container四种类型  </li>
<li><strong>--privileged=true</strong> <br>
使用该参数，container 内的 root 拥有真正的 root 权限。否则， container 内的 root 只是外部的一个普通用户权限。privileged 启动的容器，可以看到很多 host 上的设备，并且可以执行 mount 。甚至允许你在 Docker 容器中启动 Docker 容器。  </li>
</ul>

<h6 id="9">9. 确保防火墙端口开启</h6>

<pre><code class="language-shell">#打开端口/端口段(--permanent永久生效，没有此参数重启后失效)
$ firewall-cmd --zone=public --add-port=8022/tcp --permanent 
$ firewall-cmd --zone=public --add-port=8022-8022/tcp --permanent 

#重新载入
$ firewall-cmd --reload

#查看端口
$ firewall-cmd --zone=public --query-port=8022/tcp

#删除端口
$ firewall-cmd --zone=public --remove-port=8022/tcp --permanent
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/19.jpg" alt="image" title="">  </p>

<h6 id="10">10. 验证</h6>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/linux-net-core-docker/20.jpg" alt="image" title="">  </p>]]></content:encoded></item><item><title><![CDATA[隐私策略]]></title><description><![CDATA[<p>我们非常重视您的隐私，我们尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务，我们会按照本隐私权政策的规定使用和披露您的个人信息。  </p>

<p>除本隐私权政策另有规定外，在未征得您事先许可的情况下，我们不会将这些信息对外披露或向第三方提供。我们会不时更新本隐私权政策。您在同意我们服务使用协议之时，即表示您信赖我们对您的信息的处理方式。此隐私权政策旨在帮助您了解我们会收集哪些数据、为什么收集这些数据以及会利用这些数据做些什么。了解这些内容至关重要，因此我们希望您能抽出时间仔细阅读此政策。  </p>

<h4 id="">我们收集的信息</h4>

<p>我们收集信息是为了向所有用户提供更好的服务，其中既包括一些推断出来的基本信息（例如您使用的语言），也包括一些较为复杂的信息（例如您比较喜欢哪方面的新闻）。  </p>

<p>我们收集信息的方式如下：  </p>

<ul>
<li><p><strong>您向我们提供的信息。</strong> 例如，我们的某些服务可能要求您注册相关帐户，而当您注册帐户时，我们会要求您提供某些个人信息，例如您的姓名、性别、电子邮件地址、电话号码，这些信息会存储在您的帐户名下。  </p></li>
<li><p><strong>我们在您使用服务的过程中获取的信息。</strong> 我们会收集关于您使用的服务以及使用方式的信息，此类信息包括：  </p>

<ul><li><p>设备信息 <br>
我们会收集设备专用的信息（例如您的硬件型号、操作系统版本、唯一设备标识符以及包括电话号码在内的移动网络信息）。  </p></li>
<li><p>日志信息 <br>
当您使用我们的服务时，我们会自动收集某些信息并存储到服务器日志中。此类信息包括：  </p>

<ul><li>您对我们服务的详细使用情况，例如您的浏览历史。</li>
<li>IP地址。</li>
<li>设备事件信息，例如崩溃、系统活动、</li></ul></li></ul></li></ul>]]></description><link>https://lary.me/privacy-policy/</link><guid isPermaLink="false">39aabe38-8060-4908-af42-698281ac387f</guid><category><![CDATA[privacy policy]]></category><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Sat, 05 May 2018 07:42:14 GMT</pubDate><content:encoded><![CDATA[<p>我们非常重视您的隐私，我们尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务，我们会按照本隐私权政策的规定使用和披露您的个人信息。  </p>

<p>除本隐私权政策另有规定外，在未征得您事先许可的情况下，我们不会将这些信息对外披露或向第三方提供。我们会不时更新本隐私权政策。您在同意我们服务使用协议之时，即表示您信赖我们对您的信息的处理方式。此隐私权政策旨在帮助您了解我们会收集哪些数据、为什么收集这些数据以及会利用这些数据做些什么。了解这些内容至关重要，因此我们希望您能抽出时间仔细阅读此政策。  </p>

<h4 id="">我们收集的信息</h4>

<p>我们收集信息是为了向所有用户提供更好的服务，其中既包括一些推断出来的基本信息（例如您使用的语言），也包括一些较为复杂的信息（例如您比较喜欢哪方面的新闻）。  </p>

<p>我们收集信息的方式如下：  </p>

<ul>
<li><p><strong>您向我们提供的信息。</strong> 例如，我们的某些服务可能要求您注册相关帐户，而当您注册帐户时，我们会要求您提供某些个人信息，例如您的姓名、性别、电子邮件地址、电话号码，这些信息会存储在您的帐户名下。  </p></li>
<li><p><strong>我们在您使用服务的过程中获取的信息。</strong> 我们会收集关于您使用的服务以及使用方式的信息，此类信息包括：  </p>

<ul><li><p>设备信息 <br>
我们会收集设备专用的信息（例如您的硬件型号、操作系统版本、唯一设备标识符以及包括电话号码在内的移动网络信息）。  </p></li>
<li><p>日志信息 <br>
当您使用我们的服务时，我们会自动收集某些信息并存储到服务器日志中。此类信息包括：  </p>

<ul><li>您对我们服务的详细使用情况，例如您的浏览历史。</li>
<li>IP地址。</li>
<li>设备事件信息，例如崩溃、系统活动、硬件设置。</li></ul></li>
<li><p>本地存储 <br>
我们会将您的偏好设置、数据缓存、文件下载进行本地存储。  </p></li>
<li><p>在线存储 <br>
当您使用数据同步功能时，我们可能会请求访问您的网络存储空间（例如OneDrive）。  </p></li></ul></li>
</ul>

<h4 id="">我们如何使用收集到的信息</h4>

<ul>
<li><p>我们不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息，除非事先得到您的许可，或该第三方和我们（含我们关联公司）单独或共同为您提供服务，且在该服务结束后，其将被禁止访问包括其以前能够访问的所有这些资料。  </p></li>
<li><p>我们亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何我们平台用户如从事上述活动，一经发现，我们有权立即终止与该用户的服务协议。  </p></li>
<li><p>为服务用户的目的，我们会利用从所有服务中收集到的信息来提供、维护、保护和完善这些服务，同时开发新的服务并保护用户。当我们要将信息用于本隐私权政策未载明的其它用途时，则会事先征求您的同意。  </p></li>
</ul>

<h4 id="">我们分享的信息</h4>

<p>我们不会与其他任何公司、组织和个人分享用户的个人信息，但以下情况除外：  </p>

<ul>
<li><p><strong>征得您的同意</strong> <br>
在征得您的同意后，我们会与其他公司、组织和个人分享您的个人信息。我们必须您选择同意才会共享任何敏感的个人信息。  </p></li>
<li><p><strong>用于外部处理</strong> <br>
我们会向我们的关联机构或其他受信任的商家或个人提供用户个人信息，让他们按照我们的说明、隐私权政策以及其他任何相关的保密和安全措施来为我们处理上述信息。  </p></li>
<li><p><strong>出于法律原因</strong> <br>
如果我们确信：为了实现以下目的而有必要访问、使用、保留或披露相关信息，我们就会与其他公司、组织和个人分享用户个人信息：  </p>

<ul><li>遵循任何适用法律、法规、法律程序的要求或强制性的政府要求。  </li>
<li>执行适用的服务条款（包括调查可能存在的违规情况）。  </li>
<li>查找、预防或处理欺诈、安全或技术方面的问题。  </li>
<li>在法律要求或允许的范围内，保护用户或公众的权利、财产或安全免遭损害。  </li></ul>

<p>我们可能会对外公开并与合作伙伴（例如发布商、广告客户或关联的网站）分享不含身份识别内容的信息。例如，我们可能会对外公开信息，以便让公众了解我们服务的总体使用趋势。  </p></li>
</ul>

<h4 id="">信息安全</h4>

<p>我们努力为用户提供保护，以免我们保存的信息在未经授权的情况下被访问、篡改、披露或破坏。为此，我们特别采取了以下措施： </p>

<ul>
<li>我们采用适当的加密方式对许多服务进行加密。  </li>
<li>我们会审查信息收集、存储和处理方面的做法（包括物理性安全措施），以避免各系统遭到未经授权的访问。</li>
<li>我们只允许那些为了帮我们处理个人信息而需要知晓这些信息的员工、承包商和代理商访问个人信息，而且他们需要履行严格的合同保密义务，如果其未能履行这些义务，就可能会被追究法律责任或被终止其与我们的关系。  </li>
</ul>

<h4 id="">隐私策略适用范围</h4>

<p>本隐私权政策适用于由我们提供的所有服务，但另外设定隐私权政策且未纳入本隐私权政策的服务不在此列。  </p>

<p>我们的隐私权政策不适用于由其他公司或个人提供的服务，例如在搜索结果中向您显示的产品或网站、可能包含我们服务的网站或者在我们的服务中链接到的其他网站。对于为我们的服务进行广告宣传，以及可能使用 Cookie、像素标签和其他技术来投放和提供相关广告的其他公司和组织，我们的隐私权政策并未涵盖其信息处理惯例。  </p>

<hr>

<h3 id="">我们会保障您个人信息的私密性和安全性，并让您能够自主控制。</h3>]]></content:encoded></item><item><title><![CDATA[China Daily - 常见问题]]></title><description><![CDATA[<p><a href="https://www.microsoft.com/store/p/china-daily/9nblggh43dp0">China Daily</a>可以说是博主的第一个实用UWP应用，自2016年底发布以来，在各位用户的支持与帮助下，China Daily的功能得以逐渐完善，实在感谢！  </p>

<p>此篇博客的主要目的在于向各位解答一些China Daily使用过程中的常见问题，希望能解答你一些心中的疑惑。  </p>

<h6 id="">联系开发者</h6>

<p>China Daily中的Supports页面为各位提供了几种可以联系到博主的方式，各位在使用过程中如果遇到任何问题，欢迎与我联系。 <br>
<em><code>博主邮箱：dev@lary.me</code></em> <br>
<em><code>ChinaDaily用户交流QQ群：</code></em><a href="https://shang.qq.com/wpa/qunwpa?idkey=2e2ee337bbba624bfec7b8eb49f3d0485c08ffc299d1daa98eccdd2287956c75"><em><code>453649597</code></em></a> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/apps-chinadaily-faq/2.jpg" alt="image"></p>

<h6 id="">用户登录问题</h6>

<p>China Daily是一款三方应用，无法得到官方的技术支持，博主未能破解官方登录接口加密方式，所以无法实现用户登录。同时，新闻评论等需要用户登录后才能进行的操作也暂时无法实现。  </p>

<h6 id="">语音读报问题</h6>

<p>部分童鞋在使用语音读报的时候，会收到一个弹框提示，这是因为应用检测到你的电脑当前默认语音发声引擎不是英语引擎（使用英语发声的效果明显好一些，中文语音会把阿拉伯数字读成汉语发音）。 <br>
你可以在China Daily的设置页面找到所有可用的发声，分别试用下并选择你最喜欢的即可。
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/apps-chinadaily-faq/1.jpg" alt="image" title=""> <br>
如你所见，我选择了Microsoft Zira，它是在我手动安装语言包English (United States)后得到的<em><code>（重要提示：安装语言包时需要一并安装对应的语音包；如果你在China Daily的Speech</code></em></p>]]></description><link>https://lary.me/apps-chinadaily-faq/</link><guid isPermaLink="false">884b5a6b-6354-40ca-b1ab-baf3e739631d</guid><category><![CDATA[China Daily]]></category><category><![CDATA[UWP]]></category><category><![CDATA[F.A.Q.]]></category><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Sat, 14 Apr 2018 13:27:19 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://www.microsoft.com/store/p/china-daily/9nblggh43dp0">China Daily</a>可以说是博主的第一个实用UWP应用，自2016年底发布以来，在各位用户的支持与帮助下，China Daily的功能得以逐渐完善，实在感谢！  </p>

<p>此篇博客的主要目的在于向各位解答一些China Daily使用过程中的常见问题，希望能解答你一些心中的疑惑。  </p>

<h6 id="">联系开发者</h6>

<p>China Daily中的Supports页面为各位提供了几种可以联系到博主的方式，各位在使用过程中如果遇到任何问题，欢迎与我联系。 <br>
<em><code>博主邮箱：dev@lary.me</code></em> <br>
<em><code>ChinaDaily用户交流QQ群：</code></em><a href="https://shang.qq.com/wpa/qunwpa?idkey=2e2ee337bbba624bfec7b8eb49f3d0485c08ffc299d1daa98eccdd2287956c75"><em><code>453649597</code></em></a> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/apps-chinadaily-faq/2.jpg" alt="image"></p>

<h6 id="">用户登录问题</h6>

<p>China Daily是一款三方应用，无法得到官方的技术支持，博主未能破解官方登录接口加密方式，所以无法实现用户登录。同时，新闻评论等需要用户登录后才能进行的操作也暂时无法实现。  </p>

<h6 id="">语音读报问题</h6>

<p>部分童鞋在使用语音读报的时候，会收到一个弹框提示，这是因为应用检测到你的电脑当前默认语音发声引擎不是英语引擎（使用英语发声的效果明显好一些，中文语音会把阿拉伯数字读成汉语发音）。 <br>
你可以在China Daily的设置页面找到所有可用的发声，分别试用下并选择你最喜欢的即可。
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/apps-chinadaily-faq/1.jpg" alt="image" title=""> <br>
如你所见，我选择了Microsoft Zira，它是在我手动安装语言包English (United States)后得到的<em><code>（重要提示：安装语言包时需要一并安装对应的语音包；如果你在China Daily的Speech Voice可用列表里未找到合适/喜欢的发音，请自行下载语言包）</code></em>。一旦你切换成了英文发音，语音朗读时就不会再收到弹框提醒了。当然，你也可以选择点击Cancel按钮，继续使用当前发音。 <br>
语音读报功能完全由本地转换实现（无需联网），由于机器发音的缘故，语调等方面表现难以尽如人意。  </p>

<h6 id="">划词/划句翻译问题</h6>

<p>由于技术原因，手机端暂不支持划句翻译。 <br>
China Daily的单词翻译数据来自爱词霸，语句翻译数据来自有道翻译，请容我在此向翻译数据源表示崇高的敬意。同时，由于机器翻译的缘故，译文不可避免地存在不够准确的地方，望各位多多包涵。 <br>
此外，网络条件不太良好的情况下，划词翻译响应会比较慢，请大家保持耐心。  </p>

<h6 id="">磁贴问题</h6>

<p>China Daily支持透明和非透明磁贴。考虑到动态磁贴在一定程度上会破坏透明磁贴的特色，所以不支持同时打开动态磁贴和透明磁贴。此外，大号磁贴与小号磁贴不支持动态磁贴，主要是考虑到China Daily图片质量普遍不高，大号磁贴下锯齿感明显，以及小号磁贴下图片显示不清晰的问题。 <br>
动态磁贴的刷新可能会有一定的时间延迟，请耐心等待。  </p>

<h6 id="">数据同步问题</h6>

<p><code>重要说明：由于官方数据接口变化的原因，China Daily 2.x版本的数据模型无法兼容旧版，所以无法在新版中导入旧版的收藏夹数据。</code> <br>
China Daily支持将生词本、收藏夹、浏览历史同步到OneDrive。同时，考虑到国内访问OneDrive速度可能比较慢，China Daily支持将数据导出到本地/从本地导入，如果你无法忍耐同步到OneDrive时漫长的等待过程，可以先使用本地备份的方法。  </p>]]></content:encoded></item><item><title><![CDATA[【UWP】China Daily中数据同步到One Drive的实现]]></title><description><![CDATA[<p>博主开发的UWP应用<a href="https://www.microsoft.com/store/p/china-daily/9nblggh43dp0">China Daily</a>中，支持将生词本、收藏夹等数据同步到OneDrive，现在我们来看看这个功能具体是如何实现的。  </p>

<h4 id="1onedrivesdkhttpswwwnugetorgpackagesmicrosoftonedrivesdk">1. 在项目中安装<a href="https://www.nuget.org/packages/Microsoft.OneDriveSDK/">OneDriveSDK</a></h4>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/1.png" alt="image" title=""> <br>
<em><code>特别注意：请安装Microsoft.OneDriveSDK的1.x.x版本！(2.x.x版本和1.x.x版本差异比较大，前者不兼容后者。博主在这篇博客中以</code></em><a href="https://www.nuget.org/packages/Microsoft.OneDriveSDK/1.2.0"><em><code>1.2.0版本的OneDriveSDK</code></em></a><em><code>进行讲解，如果你确实希望使用最新版本SDK，本篇博客可能对你的参考价值极其有限)</code></em> <br>
<em><code>额外说明：你也可以通过包管理控制台安装OneDriveSDK。安装语法为：PM&gt; Install-Package Microsoft.OneDriveSDK -Version 1.2.0</code></em>  </p>

<h4 id="2">2. 将当前项目与应用商店关联起来</h4>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/2.png" alt="image" title="">  </p>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/3.png" alt="image" title="">  </p>

<h4 id="3sdk">3. SDK的使用</h4>

<p>Microsoft.OneDriveSDK使用<a href="https://oauth.net/2/">OAuth 2.0</a>进行用户授权管理，</p>]]></description><link>https://lary.me/uwp-chinadaily-sync2onedrive/</link><guid isPermaLink="false">d8960bca-fc0c-403d-ba1b-8a239d5a0bcc</guid><category><![CDATA[China Daily]]></category><category><![CDATA[UWP]]></category><category><![CDATA[C#]]></category><category><![CDATA[One Drive]]></category><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Sat, 14 Apr 2018 10:00:09 GMT</pubDate><content:encoded><![CDATA[<p>博主开发的UWP应用<a href="https://www.microsoft.com/store/p/china-daily/9nblggh43dp0">China Daily</a>中，支持将生词本、收藏夹等数据同步到OneDrive，现在我们来看看这个功能具体是如何实现的。  </p>

<h4 id="1onedrivesdkhttpswwwnugetorgpackagesmicrosoftonedrivesdk">1. 在项目中安装<a href="https://www.nuget.org/packages/Microsoft.OneDriveSDK/">OneDriveSDK</a></h4>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/1.png" alt="image" title=""> <br>
<em><code>特别注意：请安装Microsoft.OneDriveSDK的1.x.x版本！(2.x.x版本和1.x.x版本差异比较大，前者不兼容后者。博主在这篇博客中以</code></em><a href="https://www.nuget.org/packages/Microsoft.OneDriveSDK/1.2.0"><em><code>1.2.0版本的OneDriveSDK</code></em></a><em><code>进行讲解，如果你确实希望使用最新版本SDK，本篇博客可能对你的参考价值极其有限)</code></em> <br>
<em><code>额外说明：你也可以通过包管理控制台安装OneDriveSDK。安装语法为：PM&gt; Install-Package Microsoft.OneDriveSDK -Version 1.2.0</code></em>  </p>

<h4 id="2">2. 将当前项目与应用商店关联起来</h4>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/2.png" alt="image" title="">  </p>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/3.png" alt="image" title="">  </p>

<h4 id="3sdk">3. SDK的使用</h4>

<p>Microsoft.OneDriveSDK使用<a href="https://oauth.net/2/">OAuth 2.0</a>进行用户授权管理，一旦用户授予相关权限后，我们就可以进行相关操作了（我的China Daily向用户申请了三个OneDrive操作权限：登录、读写、离线使用权）。详细的权限管理信息请参见<a href="https://dev.onedrive.com/auth/msa_oauth.htm">微软相关文档</a>，而下方的图片则展示了文档中的关键信息供你参考 <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/4.png" alt="image" title="">  </p>

<h6 id="31">3.1 调用登录接口获取授权</h6>

<pre><code class="language- c#">// 全局的Client
private static IOneDriveClient _client;

// 所请求的权限
private static string[] scopes = new string[] { "onedrive.readwrite", "wl.offline_access", "wl.signin" };  
</code></pre>

<pre><code class="language- c#">/// &lt;summary&gt;
/// 身份验证
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static async Task&lt;bool&gt; LogInAsync()  
{
    try
    {
        _client = OneDriveClientExtensions.GetUniversalClient(scopes);
        var session = await _client.AuthenticateAsync();

        return session != null;
    }
    catch (OneDriveException ex)
    {
        Debug.WriteLine(ex.Message);

        return false;
    }
}
</code></pre>

<p><em><code>说明：我创建了一个OneDriveUtil的静态类，并在类里面实现身份验证、上传、下载的方法。所以下述（方法调用部分除外）的字段、方法都使用了static关键字进行限制。(值得一提的是：OneDriveSDK十分的人性化，因为我们不需要主动管理Session，一个全局的Client就足够了)</code></em>  </p>

<h6 id="32onedrive">3.2 上传文件到OneDrive</h6>

<pre><code class="language- c#">/// &lt;summary&gt;
/// 上传文件到OneDrive
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static async Task&lt;bool&gt; UploadFileAsync()  
{
    var file = await StorageFile.GetFileFromPathAsync($"ms-appx:///Assets/Images/{fileName}");

    return await UploadFileAsync(file, OneDriveFilePath);
}

/// &lt;summary&gt;
/// 上传文件到OneDrive
/// &lt;/summary&gt;
/// &lt;param name="file"&gt;&lt;/param&gt;
/// &lt;param name="path"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
private static async Task&lt;bool&gt; UploadFileAsync(StorageFile file, string serverPath)  
{
    if (file != null)
    {
        var stream = await file.OpenStreamForReadAsync();

        var item = await _client
                            .Drive
                            .Root
                            .ItemWithPath(serverPath)
                            .Content
                            .Request()
                            .PutAsync&lt;Item&gt;(stream);

        return true;
    }

    return false;
}
</code></pre>

<h6 id="33onedrive">3.3 从OneDrive下载文件</h6>

<pre><code class="language- c#">/// &lt;summary&gt;
/// 从OneDrive下载文件
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static async Task&lt;Stream&gt; DownloadFileAsync()  
{
    return await DownloadFileAsync(OneDriveFilePath);
}

/// &lt;summary&gt;
/// 从OneDrive下载文件
/// &lt;/summary&gt;
/// &lt;param name="path"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
private static async Task&lt;Stream&gt; DownloadFileAsync(string serverPath)  
{
    var item = await _client
                        .Drive
                        .Root
                        .ItemWithPath(serverPath)
                        .Request()
                        .GetAsync();

    var stream = await _client
                          .Drive
                          .Items[item.Id]
                          .Content
                          .Request()
                          .GetAsync();

    return stream;
}
</code></pre>

<h6 id="34">3.4 综合调用</h6>

<pre><code class="language- c#">// 数据备份功能的实现
if (await OneDriveUtil.LogInAsync())  
{
    var succeed = await OneDriveUtil.UploadFileAsync();
}
</code></pre>

<pre><code class="language- c#">// 同步功能的实现
if (await OneDriveUtil.LogInAsync())  
{
    using (var stream = await OneDriveUtil.DownloadFileAsync())
    {
        await ImportDataAsync(stream);
    }
}

/// &lt;summary&gt;
/// 通过文件流导入数据
/// &lt;/summary&gt;
/// &lt;param name="stream"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static async Task ImportDataAsync(Stream stream)  
{
    if (stream != null)
    {
        try
        {
            // 这个地方使用了图片库，一定要记得申请相关权限（见下图）
            var lib = KnownFolders.PicturesLibrary;
            var file = await lib.CreateFileAsync(OneDriveUtil.fileName, CreationCollisionOption.ReplaceExisting);

            byte[] buffer = new byte[stream.Length];
            await stream.ReadAsync(buffer, 0, buffer.Length);

            var fs = await file.OpenStreamForWriteAsync();
            fs.Write(buffer, 0, buffer.Length);

            // TODO: Notify task succeed
        }
        catch
        {
            // TODO: Notify task failed
        }
    }
    else
    {
        // TODO: Notify task failed
    }
}
</code></pre>

<p><img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/5.png" alt="image" title="">  </p>

<h6 id="35">3.5 初次运行</h6>

<p>初次运行，系统会弹框告知用户应用正在申请授权（如下图）。若成功授权，则以后一般不会再弹出提示，除非身份验证过期。
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-sync2onedrive/6.png" alt="image" title="">  </p>

<h4 id="4demo">4. Demo</h4>

<p><a href="http://files.cnblogs.com/files/lary/OneDriveDemo.rar">Download Demo</a>  </p>

<h4 id="5">5. 参考</h4>

<p>Win10开发：OneDrive SDK 的使用 <a href="http://blog.csdn.net/zmq570235977/article/details/50520247">http://blog.csdn.net/zmq570235977/article/details/50520247</a></p>]]></content:encoded></item><item><title><![CDATA[【UWP】China Daily中划词翻译的实现]]></title><description><![CDATA[<p>在博主开发的UWP应用<a href="https://www.microsoft.com/store/p/china-daily/9nblggh43dp0">China Daily</a>中有一个比较好玩的功能——划词翻译，现将其具体的实现方式拿出来与各位分享一番。  </p>

<p>首先，我们来看看划词(划句)翻译的具体表现：
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-huacifanyi/1.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-huacifanyi/2.jpg" alt="image" title="">  </p>

<p>废话不多说，接下来我们看看具体的代码实现 <br>
<em><code>注：新闻的主体是放在一个WebView里面的，所以，说白了就是解决WebView的问题（JS通知后台C#以及C#调用JS）。</code></em></p>

<h4 id="1xaml">1.XAML部分</h4>

<pre><code class="language- xaml">&lt;WebView x:Name="webView"  
    NavigationCompleted="webView_NavigationCompleted"  
    ScriptNotify="webView_ScriptNotify"  
    /&gt;  
</code></pre>

<h4 id="2jsalert">2.在后台代码中添加对JS的alert方法的监听</h4>

<pre><code class="language- c#">private async void webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)  
{
    //检测alert
    var inject = WebViewUtil.RiseNotification();

    await</code></pre>]]></description><link>https://lary.me/uwp-chinadaily-huacifanyi/</link><guid isPermaLink="false">75b2383d-bcd6-47a6-941a-6a553af1e3a9</guid><category><![CDATA[China Daily]]></category><category><![CDATA[划词翻译]]></category><category><![CDATA[UWP]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Fri, 13 Apr 2018 14:35:00 GMT</pubDate><content:encoded><![CDATA[<p>在博主开发的UWP应用<a href="https://www.microsoft.com/store/p/china-daily/9nblggh43dp0">China Daily</a>中有一个比较好玩的功能——划词翻译，现将其具体的实现方式拿出来与各位分享一番。  </p>

<p>首先，我们来看看划词(划句)翻译的具体表现：
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-huacifanyi/1.jpg" alt="image" title=""> <br>
<img src="https://res.cloudinary.com/larymao/image/upload/v1586680272/blog/posts/uwp-chinadaily-huacifanyi/2.jpg" alt="image" title="">  </p>

<p>废话不多说，接下来我们看看具体的代码实现 <br>
<em><code>注：新闻的主体是放在一个WebView里面的，所以，说白了就是解决WebView的问题（JS通知后台C#以及C#调用JS）。</code></em></p>

<h4 id="1xaml">1.XAML部分</h4>

<pre><code class="language- xaml">&lt;WebView x:Name="webView"  
    NavigationCompleted="webView_NavigationCompleted"  
    ScriptNotify="webView_ScriptNotify"  
    /&gt;  
</code></pre>

<h4 id="2jsalert">2.在后台代码中添加对JS的alert方法的监听</h4>

<pre><code class="language- c#">private async void webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)  
{
    //检测alert
    var inject = WebViewUtil.RiseNotification();

    await webView.InvokeScriptAsync("eval", new List&lt;string&gt;() { inject });
}
</code></pre>

<pre><code class="language- c#">public static string RiseNotification()  
{
    string func = @"window.alert = function(arg) {
                        window.external.notify(arg);
                    };";
    return func;
}
</code></pre>

<h4 id="3cjsalert">3.后台C#代码检测到JS的alert方法发出通知时的处理</h4>

<pre><code class="language- c#">private void webView_ScriptNotify(object sender, NotifyEventArgs e)  
{
    var data = e.Value.Trim();

    if (data.IsUrl()) // 判断字符串是否为Url的一个自定义扩展方法
    {
        // 判断点击的是图片
        LoadImage(data);
    }
    else
    {
        // 判断点击的是文本
        LoadTranslation(data);
    }
}
</code></pre>

<p><em><code>说明：此处的data是WebView通过JS方法传递过来的信息。如果点击的是图片，则传递图片的url；如果点击（选中）的是文本，则把文本传递过来。</code></em> <br>
<em><code>疑问：WebView会这么机智的将我们想要的信息准确的传递过来吗？答案是肯定的，前提是我们交给它一个锦囊告诉它应该怎么做。请往下看~</code></em>  </p>

<h4 id="4webview">4.交给WebView的锦囊</h4>

<blockquote>
  
</blockquote>

<h6 id="41">4.1 小插曲</h6>

<p>China Daily 接口返回的新闻内容主体样式及其简陋，所以我决定稍微为它补个妆（自定义了一点点样式），如下：  </p>

<pre><code class="language- c#">public static int GetBaseFontSize() =&gt; DeviceUtils.IsMobile ? 32 : 14;

public static int GetBaseLineHeight() =&gt; DeviceUtils.IsMobile ? 64 : 28;

/// &lt;summary&gt;
/// 内容样式
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static string GetContentCSS()  
{
    string commonStyle = "touch-action: pan-y; font-family:'微软雅黑';" + (DeviceUtils.IsMobile ? "padding:2px 19px 2px 19px;" : "text-align:justify;  padding:2px 16px 2px 5px;");

    string colorstyle = "background-color:#ffffff; color:#000000;" ;

    string webStyle = "body{" + commonStyle + colorstyle +
                      $"font-size:{GetBaseFontSize()}px; line-height:{GetBaseLineHeight()}px" +
                      "}";
    string css = "&lt;style&gt;" + webStyle + "&lt;/style&gt;";

    return css;
}
</code></pre>

<p><em><code>注：GetContentCSS方法只是用于给整段新闻添加一个样式，使布局看起来美观一点，大家不用太在意它（甚至也许你的应用场景根本不需要手动修改原有样式）。</code></em>  </p>

<h6 id="42">4.2 关键部分</h6>

<pre><code class="language- c#">/// &lt;summary&gt;
/// JS方法
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static string GetContentJS()  
{
    string js = @"window.lastOriginData = '';

                  function mouseUp (param) {
                      if(param == 'y') return;
                      if (window.getSelection) {
                          var str = window.getSelection().toString();
                          window.lastOriginData = str.trim();
                          alert(str);
                      }
                  };
                  function getImageUrl(url) {
                      alert(url);
                  }
                  window.onload = function () {
                      var as = document.getElementsByTagName('a');
                      for (var i = 0; i &lt; as.length; i++) {
                          as[i].onclick = function (event) {
                              window.external.notify(JSON.stringify({ 'type': 'HyperLink', 'content': this.href }));
                              return false;
                          }
                      }
                  };";

    return js;
}
</code></pre>

<p><em><code>注：这便是锦囊的主要内容了。我在GetContentJS方法中定义了几个JS的方法。其中的window.onload部分不用太在意；mouseUp部分用于在光标离开的时候，通知后台C#用户所选中的文本；而getImageUrl方法则是用于传递所点击的图片的url。需要注意的是，接口给定的内容中一般来说不太可能给出img的onclick事件，因此，这个部分也需要我们进行相关处理，请继续往下看~</code></em>  </p>

<h6 id="43imgonclick">4.3 处理img的onclick事件</h6>

<pre><code class="language- c#">/// &lt;summary&gt;
/// 处理新闻内容（主要是过滤Style和为图片添加点击事件）
/// &lt;/summary&gt;
/// &lt;param name="content"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static string UpdateContentStyle(string content)  
{
    if (string.IsNullOrEmpty(content))
        return null;

    // 正则匹配img，统一替换它的Style，并添加点击事件（此处的匹配参数仅适用于ChinaDaily接口数据，不具代表性，各位请灵活处理）
    var pattern = @"&lt;img[\s\S]*?(style='[\s\S]*?')[\s\S]*?/?&gt;";
    var matches = Regex.Matches(content, pattern); 
    List&lt;string&gt; list = new List&lt;string&gt;();

    if (matches.Count &gt; 0)
        list.Add(matches[0].Groups[1].Value);

    for (int i = 1; i &lt; matches.Count - 1; i++)
    {
        if (!list.Contains(matches[i].Groups[1].Value))
            list.Add(matches[i].Groups[1].Value);
    }

    foreach (var item in list)
    {
        content = content.Replace(item, imgStyle);
    }

    return content;
}
</code></pre>

<h6 id="44">4.4 资源整合</h6>

<p>现在我们已经拥有了实现划词翻译功能的必要模块，接下来要做的就是把他们整合起来，并保证交给WebView的是一段完整的HTML代码。  </p>

<pre><code class="language- c#">/// &lt;summary&gt;
/// 将新闻主体部分拼凑成一段完整的html
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public string ProcessNewsContent()  
{
    var newsContent = WebViewUtil.UpdateContentStyle(news.Content);

    string webContent = "&lt;html&gt;&lt;head&gt;" + contentCSS + "&lt;script&gt;" + js + "&lt;/script&gt;" + "&lt;/head&gt;" + "&lt;body&gt;&lt;div onmouseup='mouseUp()'&gt; " + newsContent + "&lt;/div&gt;" + "&lt;/body&gt;&lt;/html&gt;";

    return webContent;
}
</code></pre>

<p><em><code>简单说明：代码中的contentCSS就是上方GetContentCSS方法返回的结果，js是GetContentJS方法返回的结果，news则是用于展示的新闻，news.Content则是新闻内容，其大致模样如下：</code></em>  </p>

<pre><code>&lt;p&gt;  
    &lt;strong&gt;Today three top economists look at the state of the global economy and give their outlooks for 2017.&lt;/strong&gt;
&lt;/p&gt;  
&lt;p&gt;  
    &lt;img attachmentid=""232694"" src=""http://iosnews.chinadaily.com.cn/newsdata/news/201612/26/432220/picture_232694.jpg"" style='max-width: 100%' /&gt;
&lt;/p&gt;  
</code></pre>

<h6 id="45">4.5 交货</h6>

<p>把完整的html字符串(下方代码将其存放在变量content中)交给WebView</p>

<pre><code class="language- c#">webView.NavigateToString(content);  
</code></pre>

<h4 id="5">5. 测试</h4>

<p>至此，关键的部分均已实现，让我们按照以上套路运行一番。（假设LoadImage和LoadTranslation方法都只是将JS传递过来的信息直接显示出来）</p>

<p>我们发现，PC上正常运行毫无问题；但是，手机上却是另一番光景了——选中文本的时候没有任何反应，取消选中的时候反倒是显示信息了。（尚未搞清楚是为何）</p>

<p>因此，我采用了一个纠正措施，如下：  </p>

<pre><code class="language- c#">/// &lt;summary&gt;
/// 为wp修正文本选中问题
/// &lt;/summary&gt;
public async void ProcessTextSelected4Phone(string text)  
{
    if (DeviceUtils.IsMobile)//wp上表现和电脑上不一样...
    {
        // 判断是否选中文本
        DataPackage dataPackage = await webView.CaptureSelectedContentToDataPackageAsync();
        if (dataPackage == null)// 表示未选中文本
        {
            LoadTranslation(null);// 隐藏翻译栏
            return;
        }

        var handleMouseUp = string.IsNullOrEmpty(text) ? false : true;
        await webView.InvokeScriptAsync("mouseUp", new[] { handleMouseUp ? "y" : "" });// 若参数为y，则不触发事件
    }
}
</code></pre>

<p><em><code>注：实质就是在手机端，在正常的mouseUp触发基础上，通过代码主动多触发一次mouseUp，以达到修正目的。</code></em>  </p>

<h4 id="6">6. 结语</h4>

<p>到这里，划词翻译中的划词提取部分已完全呈现给了各位，至于其中的翻译部分，就不再此篇博客中赘述了，各位可以自行寻找一些翻译渠道来完成（PS：博主采用的是有道的免费翻译接口，虽接口调用频率有限，倒也是够用了）。</p>

<h4 id="7demo">7. Demo</h4>

<p><a href="http://files.cnblogs.com/files/lary/Demo.rar">Download Demo</a></p>

<h4 id="8">8. 参考</h4>

<p>【WP8.1】WebView笔记：<a href="http://www.cnblogs.com/bomo/p/4320077.html">http://www.cnblogs.com/bomo/p/4320077.html</a></p>]]></content:encoded></item><item><title><![CDATA[花开成景，花落成诗]]></title><description><![CDATA[<p>文/雪中傲梅</p>

<p>沏一杯香茗，依窗而坐</p>

<p>看着窗外淅淅沥沥的小雨</p>

<p>翻看着一篇篇潮湿的日志</p>

<p>一笺笺的墨香，行行浸透着愁绪</p>

<p>一道道墨迹，深深烙在脑海</p>

<p>墨香里的情缘，温暖一世</p>

<p>展一页素笺，以墨为词</p>

<p>用宋词的优雅，舒展心中的清愁倚窗而</p>

<p>以爱为笔，以思念为章</p>

<p>将莲的心事，</p>

<p>安放在最柔软的心底</p>

<p>*</p>

<p>温一壶相思，独坐窗外</p>

<p>与文字相依，伴墨痴缠</p>

<p>月光轻柔似水，</p>

<p>照射出晶莹泪珠</p>

<p>风过飘香，泪滴成诗</p>

<p>谁能读懂情的誓言</p>

<p>谁能看懂爱的手语</p>

<p>那生疼的文字</p>

<p>是不敢碰触的记忆</p>

<p>*</p>

<p>太多的忧伤</p>

<p>抵不过尘世的荒凉</p>

<p>太多的欢喜</p>

<p>也敌不过岁月的荒芜</p>

<p>风起的夜，不知如何整理</p>

<p>思念在心灵深处渗透出泪滴</p>

<p>若，花开只要一季</p>

<p>那么，我就</p>

<p>以一朵花的姿态行走</p>

<p>穿越季节轮回</p>

<p>花开成景，花落成诗</p>

<p>不问花开几许</p>

<p>只问浅笑安然</p>]]></description><link>https://lary.me/hua-kai-cheng-jing-hua-luo-cheng-shi/</link><guid isPermaLink="false">5c6f25fd-600f-4be2-bd58-c87d849b4b2d</guid><dc:creator><![CDATA[小醉魔]]></dc:creator><pubDate>Thu, 16 Mar 2017 01:41:58 GMT</pubDate><content:encoded><![CDATA[<p>文/雪中傲梅</p>

<p>沏一杯香茗，依窗而坐</p>

<p>看着窗外淅淅沥沥的小雨</p>

<p>翻看着一篇篇潮湿的日志</p>

<p>一笺笺的墨香，行行浸透着愁绪</p>

<p>一道道墨迹，深深烙在脑海</p>

<p>墨香里的情缘，温暖一世</p>

<p>展一页素笺，以墨为词</p>

<p>用宋词的优雅，舒展心中的清愁倚窗而</p>

<p>以爱为笔，以思念为章</p>

<p>将莲的心事，</p>

<p>安放在最柔软的心底</p>

<p>*</p>

<p>温一壶相思，独坐窗外</p>

<p>与文字相依，伴墨痴缠</p>

<p>月光轻柔似水，</p>

<p>照射出晶莹泪珠</p>

<p>风过飘香，泪滴成诗</p>

<p>谁能读懂情的誓言</p>

<p>谁能看懂爱的手语</p>

<p>那生疼的文字</p>

<p>是不敢碰触的记忆</p>

<p>*</p>

<p>太多的忧伤</p>

<p>抵不过尘世的荒凉</p>

<p>太多的欢喜</p>

<p>也敌不过岁月的荒芜</p>

<p>风起的夜，不知如何整理</p>

<p>思念在心灵深处渗透出泪滴</p>

<p>若，花开只要一季</p>

<p>那么，我就</p>

<p>以一朵花的姿态行走</p>

<p>穿越季节轮回</p>

<p>花开成景，花落成诗</p>

<p>不问花开几许</p>

<p>只问浅笑安然</p>]]></content:encoded></item></channel></rss>