自定义 Bootstrap 5 的风格,实现自定义风格页面开发

f30a202d97fcb737a80ade314ebdb8e0 1

和更加自定义化的 TailwindCSS 相比,Bootstrap 显然更具备主题和模板页面开发的潜力和更易于进行模板页面的开发 —— 毕竟各种样式都已经封装好了,你可以直接使用诸如 btn 来定义一个 Button。

我有在想,提供一个基于 TailwindCSS 的 StarterKit 来快速基于 TailwindCSS 开发出一套自己的样式库,但仔细想想,其实意义不大。因为可选项太多了,想要自定义化其实没那么容易。

白宦成

而在开发 Bootstrap 的时候,大家往往会遇到的一个问题的是 —— Bootstrap 开发出来的页面千篇一律,一千个人有一千个哈姆雷特,但一千个 Bootstrap 页面却长的一模一样。而这背后的原因,主要是因为大部分人使用的是标准版的 Bootstrap —— 也就是官方所定义的样式。大家都使用一样的样式,自然就使得开发出来的页面显得雷同。

不过,其实 Bootstrap 的风格自定义并不困难。

前情提要

其实大家不去做自定义风格开发的原因倒是可以理解 —— 锅都要给 Node Sass,在 Bootstrap 5 以前,Bootstrap 使用 Node Sass 来进行样式的构建,但 Node Sass 是一个基于 C++ 编写的拓展,这使得 Node Sass 的安装十分麻烦,加上特有的网络环境,使得 Node Sass 的安装十次有九次都是无法成功安装的,大家自然也不愿意使用 Node Sass 构建的项目(包括我自己,都会刻意选择使用 Less 构建的项目)。

不过,从 Bootstrap 5 开始,Bootstrap 项目开始使用 Dart Sass 进行样式风格的开发。这使得 Bootstrap 的构建不会像过去那么痛苦,因此也可以更好的进行风格的自定义。对于如今还在使用 Bootstrap 进行项目构建的同学来说,无疑是个好消息。

自定义 Bootstrap 5 的风格

关于如何在你的项目中引入 Boostrap 的 Sass 来满足自定义的需求,可以参考官方所给出的 Customize Sass 的说明,官方提供了包括 WebpackParcel 等的接入说明,如果你连基本的配置都懒得做,官方还提供了一个空白项目,方便你 Fork 后再修改。

以我自己使用的 Next.js 为例,配置起来也比较简单:

1. 安装相关依赖

在 Next.js 项目根目录安装所需依赖

yarn add bootstrap
yarn add -d sass sass-loader

2. 创建 Scss 文件

styles 目录下创建一个新的 globals.scss 文件,并在其中加入如下代码

// custom variables
@import "../node_modules/bootstrap/scss/bootstrap";
// custom css code 
Code language: JavaScript (javascript)

其中 custom variables 的部分是让你用来加入自定义的变量的,比如自定义 Primary 的颜色、自定义 Padding 等一系列操作,都需要在 Bootstrap 的引入前定义,这样才能确保你的引入变量会生效。

而后面的 custome css code 则是可以用来定义一些你自己编写的,没有被覆盖在 Bootstrap 内部的类。

3. 在项目中引入创建好的 scss 文件

在项目的 pages/_app.js 引入我们刚刚创建好的 scss 文件,从而确保应用在启动的时候可以自动构建 Bootstrap ,并引入至项目中。

import '../styles/globals.css'
import '../styles/globals.scss' // 这一行是新增的
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
Code language: JavaScript (javascript)

4. 在项目中使用 Bootstrap 类进行开发

当你完成上述的配置,就可以在自己的项目中引入相应的类来进行开发了。

一些小技巧

使用 Themestr.app 进行风格的快速设定

Themestr 是一个帮助你快速生成一套Bootstrap5自定义风格的工具网站。

d2b5ca33bd970f64a6301fa75ae2eb22 2

在 Themestr 中,提供了 UI Builder、Themer 和 Customizer 三个不同的工具,其中:

UI Builder 提供了一个简单的 4 步选择器,让你定义了基本的颜色、字体、ICON 和 按钮风格,从而形成一套独特的 Bootstrap UI。

d2b5ca33bd970f64a6301fa75ae2eb22 3

Themer 则提供了更加全面的样式的设定和预览功能,你可以在右侧的选择界面简单的配置样式,并在左侧自动刷新的页面中查看你的配置所实现出来的效果,方便你快速找到看到你的配置所能展现出的效果,简单且直观。

d2b5ca33bd970f64a6301fa75ae2eb22 4

Customizer 则提供了全面的 Bootstrap 的变量,方便你快速找到你要修改的变量,并帮助你生成对应的 Sass 配置文件,方便你在此修改变量,并在本地进行开发。

d2b5ca33bd970f64a6301fa75ae2eb22 5

对于不同的场景,Themestr 提供了不同的工具来帮助你快速开发,是一个非常不错的 Bootstrap 主题开发的辅助工具。

如何找到需要修改的变量

Bootstrap 在文档中针对每一个 Component 都提供了相应的 Sass Variables 的说明,你只需要在文档中找到你要修改的组件, 并在你自己的 global.scss 中修改对应的变量,就可以实现自定义。

d2b5ca33bd970f64a6301fa75ae2eb22 6

如何找到特定类的源码

在进行 Bootstrap 开发的时候,如果有些类你找不到样式的时候,一个很好的方式是开启开发环境,并在开发环境中可以看到对应的 scss ,并在对应的 scss 当中,点击链接跳转到对应的 scss 定义,即可看到对应的样式的源码。

d2b5ca33bd970f64a6301fa75ae2eb22 7
d2b5ca33bd970f64a6301fa75ae2eb22 9

在跳转后,有些时候你发现似乎并不是直接定义的样式,比如下图,则说明这样的类是通过 map 来批量生成的,你就需要修改对应的 map 来完成自定义。

d2b5ca33bd970f64a6301fa75ae2eb22 8

如何自定义主题颜色、字体大小、间距等批量生成的类

在 Bootstrap 中存在一些批量生成的类,比如 theme-colorfont-sizefont-weight 还有 paddingmargin 等等。

这些类是 Bootstrap 使用 @each 来生成的,而生成的依据,则是 Bootstrap 的各种 map 来完成的。因此,你想要进行定义,则需要修改对应的 Map。你可以使用 map-merge 来向 map 中添加新的选项

// Create your own map
$custom-colors: (
  "custom-color": #900
);

// Merge the maps
$theme-colors: map-merge($theme-colors, $custom-colors);
Code language: PHP (php)

而如果你想要移除一个选项,则可以使用 map-remove 方法来完成

// Required
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root";

$theme-colors: map-remove($theme-colors, "info", "light", "dark");

// Optional
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
// etc
Code language: JavaScript (javascript)

OneInStack 中 Redis 的使用注意事项

0dbb4980acb58d4396e9a2055bf2176e

在使用 OneInStack 时,有一些事项需要注意,不然可能会导致你的 Redis 在使用过程中出现问题。

1. Redis 默认配置内存是 122 MB

OneInStack 中 Redis 的默认内存配置是比较小的,只有 122MB ,对于一些大型应用来说,是肯定不够的,因此,在实际使用过程中,还是最好将其修改为一个更大的值。

配置文件路径: /usr/local/redis/etc/redis.conf

需要修改的项目 maxmemory 122000000,将 122000000 修改为 256000000 即可将 Redis 使用的内存设置为 256 MB,从而扩大了整体可用的内存的大小,应对大型数据库也游刃有余。

2. Redis 的逐出机制为 noeviction

OneInStack 中 Redis 的默认逐出机制是 noeviction,即内存满后不逐出,写入缓存报错,读缓存不受影响。

对于将 Redis 作为数据库的场景而言,这么干是正确的。但对于做缓存的场景,则需要修改逐出机制,比如可以将逐出机制修改为 allkeys-lru,即在全部的key中淘汰最近最少使用的key。

在配置文件( /usr/local/redis/etc/redis.conf)中加入一行配置即可

maxmemory-policy allkeys-lru

那些影响你前端开发体验的问题(1)

code 1076536 640

最近在研究和体验一些 GitHub 上的前端项目,遇到了一些让人体验不佳的点, 这里梳理一些我遇到的,一方面是留存,记录那些让我体验不好的事情。另一方面警示自己在开发前端项目的时候应该注意一下开发体验,确保不会让别人在开发的时候也遇到这些问题。

1. 项目应该有文档

这是不少 GitHub 上面项目的普遍问题。现有的文档要么是以 Framework 生成的 Readme 为主,要么直接没有文档。

但没有文档对于后来者的开发其实非常不友好,比如几个常见问题:

  1. 如何启动一个 Server & 如何设置后台 API 的 Prefix?
  2. 项目的组织结构是什么样的?
  3. 涉及到的第三方服务应该如何配置?

上面这些文档是帮助后来者快速使用你的项目所必需的(换句话说,如果你要做开源项目,想要打造开源社区,这也是必要的)。

2. 项目控制算法复杂度

我在开一些前端项目的时候,明显会感到浏览器的响应变慢了,我以为是我的 Mac Mini 2012 性能不行。但我打开自己的博客,依然如丝般顺滑,我就明白了。问题不在于我的电脑性能跟不上,而是这个前端页面加入了大量的有用或无用的代码,使得 CPU 需要大量的计算。

虽然进入 SPA 的时代以后,我们开始将算力的使用从服务端往客户端转移,但也建议各位工程师控制自己项目的复杂度,不要一开网页,风扇就呼呼的转。浏览体验不好,还凸显自己的技术不行。

3. 使用 debuuger 调试,而不只是 console.log

在编译型语言当中,使用 Debugger 来进行调试是一项基本技能,因为需要在运行时去看不同的变量的值,以此来完成调试。

d2b5ca33bd970f64a6301fa75ae2eb22
爆炸的 Console

但在前端领域,因为 console.log 的过于好用,大家开始习惯于使用 console.log 来打印变量,但对于大型项目来说,大量的 console.log 也会让你的项目输出许多无意义的内容,让整个项目的调试和开发受到阻碍。

总结

最近看项目,总结了三条自己觉得影响体验的点,后续随着看的项目的深入,会逐渐总结出更多影响体验的点,引以为鉴。

WordPress Contact Form 7 的正确用法

grayscale photo of person using MacBook

Contact Form 7在实际使用过程中,有些人为了简化表单的设计和使用,会选择直接在多个地方使用同一个表单,并通过页面选择器来实现不同的样式(常见于基于 Elementor 构建的页面)。

但这样会造成长期的可维护性问题:

  1. 每个页面单独写样式选择器来控制样式,且在样式命名方面没有习惯的话, 会导致页面的样式混乱,修改成本较高。
  2. 不同的页面的样式采用了相同的类名,在进行样式修改时,交接性极差。

如果想要解决这个问题,有两个方式:

  1. 规范页面样式类规范:不同风格和样式的按钮采用不同的类名,如(.blueBtn.yellowBtn);
  2. 多处使用的表单,采用复制的方式,而不是使用同一个表单:Contact Form 7 提供了 Duplicate 功能,可以非常方便的复制一个表单,并根据表单的位置来命名。降低配置表单的成本。
d2b5ca33bd970f64a6301fa75ae2eb22 14
Duplicate 一个表单

从一封钓鱼邮件聊起:针对普通人的钓鱼邮件设计

black laptop computer

收到了一封钓鱼邮件,刚好最近没有什么内容要写,就聊聊这一封钓鱼邮件。

我收到的这一封钓鱼邮件是这样的

d2b5ca33bd970f64a6301fa75ae2eb22 9

接下来看看里面的钓鱼邮件设计的三个巧妙之处:

1. 针对独立域名的钓鱼邮件

我的对外的邮箱目前使用的是 bestony@linux.com 的邮件,而由于 Linux.com 邮箱设计,实际上并不会有一个邮箱给你使用,而是你可以选择一个邮箱地址,系统会将发送到这个地址的邮件自动转发给你,我将邮件转发到了我自己的 Google 邮箱当中。

所以实际上我收到的邮件有两种:以 gmail 地址收到的邮件和以 linux.com 地址收到的邮件。

可以看到,上面的这个邮件当中我的收信地址是 linux.com 的地址,而不是我的 gmail 地址。

这正是这封邮件设计的巧妙之处:为特定人群发送特定内容的钓鱼邮件。试问自己:如果你的 QQ 邮箱收到了上述的邮件,你会把他当成是企业给你发送的安全邮件么?显然不会,因为你知道, QQ 不会给你发送这样的邮件。

但如果你的邮箱刚好是一个自定义域名,且刚好你所在的企业的 IT 并没有拦截到这封邮件,那么这封邮件对于那些安全意识不高的人来说,马上就会中招。

2. 使用了一个内网的地址来降低警惕度

对于绝大多数人来说,可能对于内网地址和外网地址没感知。可能直接就点击进去了。但对于一些对于计算机网络略有耳闻的人来说,可能会熟记的一个地址是 192.168.0.1,这个地址被不少的路由器作为默认的地址和网关地址来使用,从而成为不少人的心中的安全地址。

邮箱中的 192.168.22.23 这个地址就会让一些人放下警惕,然后点击进去查看内容 —— 然后成功的掉进陷阱中。实际上邮件只是用内网地址来作为一个表面展示的文字,真正的链接地址是 http://szfdxled.com/function/uploadfile/20220412/20220412000529_78464.html#bestony@linux.com

这提醒了我们,如果邮件当中有链接,最好复制出来,而不是直接点进去(说不定别人替换了呢?)

d2b5ca33bd970f64a6301fa75ae2eb22 10

3. 自动识别的邮箱域名

我点击链接进去以后注意到,他在顶部加入了对应的邮箱域展示和 Icon 的展示。设计的挺巧妙。

如果点击进去展示的是无关的域信息,可能你也不会点击进去查看。但如果展示的是你自己的邮件域,会进一步放松警惕(特别是你以为你点击进去的其实是 192.168.xxx.xxx 时)。

对于意识不太强,或者平时不常使用网页版的人来说,可能真的就直接输入账号密码登录了。我还试了试,如果把 URL 后缀的邮箱域修改了,还会自动替换邮箱域和对应的 icon(应该是抓的 favicon,但不知道为啥抓到的是这个。

d2b5ca33bd970f64a6301fa75ae2eb22 11

不过,也有一些设计的比较蠢的地方

1. 用了 Reply To 的字段暴露了自己的信息

在看这封邮件的时候,我注意到他设定了 Reply To 字段。Reply To 当中暴露了自己的 QQ 邮箱。

d2b5ca33bd970f64a6301fa75ae2eb22 12

然后我搜了一下,发现能搜到这个人,看起来似乎还像是正常使用的 QQ 号。。。如果是一个经验丰富的 Cracker ,可能会选择使用一个更加安全的沟通方式。

d2b5ca33bd970f64a6301fa75ae2eb22 13

总结

这封钓鱼邮件中给我不少的启发,里面的一些设计也很有意思。但对于有充足安全意识的人来说,这些问题确实都可以规避掉,从而不受诈骗的影响。此外,Cracker 的产品设计值得我们学习,一些好的设计,确实可以帮助用户更省事。

为什么硬件产品总有遗憾?

macro photography of black circuit board

在做软件产品时,我们会无限的追求极致,希望将产品的体验、用法等各个方面全部做到最好,因为软件的交付成本低于硬件的交付成本,我们只需要一个命令,就可以将旧版的软件升级到新版。因此,我们可以通过不断的软件迭代,来将一个不完美的软件,迭代到完美的状态下。 而硬件则不同,硬件涉及到两个问题:

  1. 硬件存在供应链和备货的问题:这就会要求你必须提前采购物料,并进行研发。物料是实体的,必然会存在出问题的可能,因而会让我们的硬件设备出现各种各样奇奇怪怪的问题,天然就不太可能完美。
  2. 硬件存在升级成本的问题:硬件和软件的一个很不同的是,硬件往往是很难做到很好的升级的,想要升级,就不得不重新购买,而购买是需要花费金钱的。不是每个人都能购买新的设备的。而从厂商的角度来看,假设他做了一款完美的产品,他就无法再继续售卖同类型的产品,自然也没有动力去推出一款「完美」的产品

上述两个原因,导致我们永远不可能买到完美的硬件产品。

做 Leader,别做 Boss

a37ba5bfa7e4049591bed4e6ca15b9ac

在做管理时,不要想当然的以为 Ctx 已经传递了。还是会有很多不明确的信息。

如果你希望更多的人帮你去做事,需要传递足够做的 Ctx 出去。可能这些你过去觉得不重要的 Ctx,但对于那些不懂的同学来说,这就是必要的 Ctx。

如果只有一个任务,那你是 Boss ,最终会众叛亲离。但如果你给到的是一个完整的资料,那你会成为 Leader,领导别人去做成一件事, Empower 别人去做一件事。

受教。

工单效率这么低,为什么还要选择工单服务?

white crt tv turned on beside white remote control

为什么觉得工单的效率低?

和语音聊天、 IM 沟通,工单系统作为一个「问答」形态的产品,必然带来是消息回复的不那么及时,你也很难像 IM 一样,一波语音通话,直接打过去,让对方的同学接听。 但,这就意味着效率低么?

也不尽然,我们觉得语音通话和IM 沟通能够提高效率是因为 能够在很短的时间内尽可能多的传递信息给对方。但这并非语音通话和 IM 沟通才能提供的,实际上,我们用好异步交流的方式,同样,甚至可以更好的传递信息。正是因为你将工单系统当作 IM 系统来使用,才造成效率低。

什么是好的工单系统的用法?

工单系统的异步沟通回复实效性差,但相应的,也倒逼我们必须在尽可能少的消息当中补充足够多的上下文,帮助工单服务人员来更好的协助我们巡查问题。比如,附带上出问题的条件和代码、提供信息帮助对方定位、提供复现的方式(是不是觉得很眼熟?我们在开源社区也被要求在提交 issue 的时候尽可能多的提供信息)。 当我们提供了尽可能多的信息的时候,我们才能更好的让工单系统起到作用,才能更好的传递信息,解决我们的问题。

工单引发的全局最优和局部最优的思考

从工单系统出发,我又想到了一个话题 —— 「全局最优和局部最优」。我们常说,要追求全局最优解,而不是局部最优解。就比如腾讯停止某个不那么擅长业务,显然,从局部来看,对于负责对应业务的同学来说,这不是一个最优解。但对于公司层面来看,释放人力去做更有价值的事情才是最优解。

为什么会选择工单?

和语音通话/IM 沟通相比,工单系统的优势在于何处?语音通信和 IM 沟通虽然可以很方便的将信息传递,但对于信息的沉淀和再次利用,并没有什么帮助。实际上用户并不会主动在历史当中搜索。

你会发现,用户并不会主动搜索历史问题,他们更喜欢直接提出问题,然后等待解决。 既然如此,那工单系统所承载的服务,自然也就能够提供不同的和更加强大的能力,帮助 oncall 同学来解决服务的压力。

工单并不完美

当然,现在的工单系统并不能做到想要的最优,比如在我看来,这里还有很多东西是我们没有做到的,比如自动记录用户进入 oncall 页面的 path(这样 oncall 同学可以直接知道用户是在哪个模块出现的问题)、自动记录提示用户选择 service id(这样就不用每次都提醒用户输入 service id)、自动根据用户输入的信息提供可能的回复。

可以看到,这里面其实是有很多的 gap 没有处理好的。也正是这些没有处理好的事情,让现在的 oncall 系统看起来不那么的美好。 但你我都知道,事情总是在演进的,现在不好不意味着他不能让事情变得更好。让工程师直接提供 oncall 固然可以让服务变得更好,但对于业务的可持续性、成本的优化和不断的产品迭代来看,并不能算是一个最优解。

而客服系统其实是一个标品,我们会看到 ZenDesk、Tawk.to、Intercom等等一系列的客服系统,都在提供服务,从某种角度上来看,这验证了客服系统/工单系统的方向的正确性。而一个好的客服系统,应该是让用户很快得到帮助(比如自动帮助用户搜索问题),让工程师可以更加专注(比如把问题的解决更好的总结成经验)。

使用 Stylish 巧妙破解基于 CSS 的复制限制

person using black laptop computer

本文内容仅用于学习和技术研究。请不要用此作恶。

白宦成

在一些网站上,出于版权或增长的诉求,一些网站会在站点内部加入防止复制内容的限制。作为一个内容创作者,我是支持(因为版权原因而不得不选择)这样的行为的。但另一方面,我自己也需要进行一定的笔记,对于长段的内容,显然,直接复制原作者的内容更适合我。因此,我也不得不研究一下,如何破解这种样式的限制。

为什么 CSS 能禁止复制?

CSS 当中提供了一个属性 user-select 可以用于设定文字是否可以被选中,对于非技术人员来说,无法被选中,自然无法使用 Ctrl + C 来复制内容。在这种情况下,对于非技术人员,就可以实现很好的防复制了。

不过,CSS 防复制不算多见,我见的最多的还是以 Javascript 的方式来禁用复制和 Devtools 的。

这个属性的值有以下五种,其中 none 是用于防止复制的,设定为 None 后,即使你的内容是文本内容,也无法被选中,就无法直接使用 Ctrl + C 来完成复制了。而 text 则是可以选择文本,其他的几个属性,你可以在 MDN 的语法说明中找到。

user-select: none;
user-select: auto;
user-select: text;
user-select: contain;
user-select: all;
Code language: HTTP (http)

使用 Stylish 来破解 CSS 的限制

类似油猴脚本可以在目标网页上加载 Javascript 文件,Stylish 可以在目标网页上加载 CSS ,并按照 CSS 的计算权重,让我们的代码可以得到执行。基于这个能力,我们可以覆盖原本网页上样式,来实现将 user-select 的值修改为我们需要的值。

1. 安装 Stylish

前往 Google Chrome Extension Store 安装 Stylish

2. 找到不能复制的文字对应的样式类

在浏览器中打开开发者工具,定位到你需要复制的文本元素,在右侧的调试工具中切换到「计算样式」,就可以在其中找到 user-select属性,并可以看到对应的样式类选择器,复制选择器。

d2b5ca33bd970f64a6301fa75ae2eb22 8

3. 在 Stylish 中新建一个样式文件,并在其中添加对应样式类的代码

因为我们只需要修改复制,所以只需要将对应的属性的值做一个简单的修改即可。

d2b5ca33bd970f64a6301fa75ae2eb22 9

4.设置生效网站

为了减少这段代码影响到的页面,你可以在下方的应用对象这里设定具体生效的网站,从而降低对于其他站点的影响。

d2b5ca33bd970f64a6301fa75ae2eb22 10

总结

本次针对 CSS 禁止复制的研究帮助我节省了大量的研究的时间,有效的提升了我进行研究的效率,真的不错!

使用 Laravel Envoy 进行项目部署

6e6105f2289088e26d4c862e51620646

在大型企业当中,往往会有各种各样的 CI & CD 工作流来完成项目的部署,而对于我自己的项目而言,我也希望能够有这样顺畅的交付体验,因此,我也为自己的业务加上了类似的方案,以实现顺滑的开发体验。

不过,我没有做的那么重,没有上 Ansible,也没有封装 Docker,就是标准的 LNMP 环境。只不过,我在标准的环境上加入了 Laravel Envoy — 一个快速部署工具

什么是 Laravel Envoy

Envoy 是 Laravel 团队开发的一个在远程服务器上执行某些命令的工具,因为他同样使用 Blade 语法,因此在 Laravel 项目中接入非常方便。

如何使用 Laravel Envoy

1. 安装 Laravel Envoy

Envoy 有多种使用方式,一种是跟随项目的使用方式,即使用 composer require laravel/envoy --dev 来安装,并使用 php vendor/bin/envoy 的方式来运作,这样的好处是你无需污染顶级目录,只在项目层面使用 Envoy 来进行部署。

不过我因为有很多项目,所以我会更加倾向于直接在全局安装 envoy,执行 composer global requre laravel/envoy 实现在全局安装 Enovy。全局安装完成后,当你需要执行命令,只需要执行 envoy run deploy,就能执行当前目录下所定义的命令。

除了全局安装,你也可以考虑使用 Bash alias 来实现类似的效果,将 envoy alias 给 php vendor/bin/envoy 就可以实现即使没有全局安装,也可以实现不需要手动输入前缀的特性。

2. 初始化配置文件

当你需要配置时,你要做的很简单,只需要执行 envoy init 服务器地址/昵称就可以获得一个 Envoy.blade.php 文件,你可以通过修改这个文件的内容,来实现定义不同的工作。同样的,你可以将这个文件纳入到版本控制系统当中,从而实现部署脚本的跟踪和定义,对于后续的持续维护比较有帮助。

因为 Envoy.blade.php 也是采用了 Blade 的后缀,所以也同样适用了 Blade 引擎,你可以通过一个语意更加友好的方式来定义你的脚本。

@servers(['web' => '127.0.0.1'])

@task('deploy')
    cd /path/to/site
    git pull origin master
@endtask
Code language: PHP (php)

比如,上面的脚本就是一个最基础的脚本,首先定义了这个脚本的命令会在名为 web 的服务器上执行,而这个服务器的地址是 127.0.0.1;并定义了一个任务 deploy。

3. 执行部署命令

当你完成代码的编写,并提交了 Git 之后。执行命令 envoy run deploy ,就会自动的在 web 这个服务器上执行在 deploy 当中执行的命令。

deploy 的命令只要是正常的 bash 命令即可。

高级用法

1. 引入其他任务

因为 Envoy 使用 Blade 语法来编写,你也可以使用 Blade 语法中的命令来引入其他文件的中的任务。因此,你可以将自己常用的命令定义出来,并将其通过 Packagist 发布出去,并在实际应用过程中,引入对应的文件来使用。

@import('vendor/package/Envoy.blade.php')
Code language: JavaScript (javascript)

2. 处理多个服务器

Envoy 支持定义多个服务器以及并行执行,对于架构相对复杂的业务,你可以定义不同的 servers ,并在对应的 task 当中来设定需要在哪些 Server 上执行。定义任务时,如果你传入了 parallel 参数,则可以控制命令同时在多个服务器上并行(该属性为 false 时则是串行(

@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
 
@task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true])
    cd /home/user/example.com
    git pull origin {{ $branch }}
    php artisan migrate --force
@endtask
Code language: PHP (php)

3. 定义变量或引入其他功能函数

你可以在 Envoy.blade.php 中加入 @setup 来完成变量的定义,并在后续的代码中使用。

@setup
    $now = new DateTime;
@endsetup
Code language: CSS (css)

也可以直接使用 @include 来引入文件

@include('vendor/autoload.php')
 
@task('restart-queues')
    # ...
@endtask
Code language: PHP (php)

4. 使用命令行参数

在启动时,如果你使用诸如 --param=value这样的参数来调用,则可以在你的代码中直接读取对应的变量,从而实现执行时带入不同的参数。

更多用法

Envoy 当中还有更多的用法,这里不再一一列举,如果你感兴趣,可以直接参考其官方文档,了解如何使用 Envoy 达成你的部署目标。

总结

在你不是很着急使用 Ansible 或 更重的部署工具的时候, Envoy 是一个不错的选择。特别是你使用 Laravel 进行开发时,Envoy 就是一个有效的工具。

什么样的业务用什么样的架构

man in black long sleeve shirt sitting on black chair

多年来,我一直是 PHP 的拥趸,不论中间我写过多少编程语言,但我始终觉得,PHP 能从 2000 年活到现在,是有其意义和价值的。

而到了现在,我终于可以回答这个价值和意义到底是什么 —— 什么样的业务,使用什么样的架构

作为技术创业者,我们最常遇见的问题,便是过度设计技术方案:我的项目是不是应该做个数据库主从备份?我的项目是不是应该做 Kuberentes 集群来处理弹性?

但实际上,对于绝大多数的业务而言,这些问题都是根本不重要的 —— 如果没有 Kubernetes,我们能否保证我们的业务的稳定性?如果没有主动备份,我们能否保障我们的业务可以平稳运转?

技术在绝大多数的业务中,都是一个成本中心,是一个保障业务正常运转的基石。他应该被重视,但不应该被过度重视。一个创业公司可能会因为业务不够稳定而失去用户,但绝不会因为业务非常稳定而留住用户。用户在乎的是你为用户解决了什么样的问题,而不是用了什么样的架构。

过度(提前)优化是万恶之源。

从头梳理,看看中国的 ICP 备案制度

26a5088bedce6368203d40ac619ac827

对于生活在中国互联网环境中的开发者来说,ICP备案(后称「备案」)是一个很难绕过去的话题,但凡你需要正经的建设一个网站,那么备案就是一个必须要做的事情,不然可能会出现各种奇奇怪怪的问题。但,你是否思考过,为什么我们需要备案?你是否了解过备案二字背后的深层含义?

为什么我们需要备案?

备案的要求和历史,可以追溯到 2000 年 9 月 20 日国务院第 31 次常务会议通过的一个管理办法 —— 《互联网信息服务管理办法》(后称信息管理办法),由时任总理朱镕基先生签发。后续我们所进行的一切备案工作,其实都是这个信息管理办法的延展。

在《互联网信息服务管理办法》当中,第二条说明了什么样的情况才需要进行备案:在中国境内从事互联网信息服务活动。

第二条 在中华人民共和国境内从事互联网信息服务活动,必须遵守本办法。

互联网信息服务管理办法, 2000 年 9 月 20 日

这一条也解释了,为什么 Facebook、Google这些网站也可以正常运转,因为他们严格意义上来说,并不算在中国境内从事相关的信息服务活动(但也没保证你一定能够访问)。同样的,如果你自己的网站托管在海外的服务器上,一样不需要备案(但同样不保证你一定能访问,毕竟你不在境内提供服务,网络出现波动啥的非常正常)。

圈定了范围之后,第三条规定了不同的互联网信息服务都是什么样的类型,进行了定性操作。

第三条 互联网信息服务分为经营性和非经营性两类。经营性互联网信息服务,是指通过互联网向上网用户有偿提供信息或者网页制作等服务活动。非经营性互联网信息服务,是指通过互联网向上网用户无偿提供具有公开性、共享性信息的服务活动。

互联网信息服务管理办法, 2000 年 9 月 20 日

而在完成了定性操作后,紧接着在第四条声明了不同服务类型应该执行的策略:经营性采取许可制度,非经营性采取备案制度。

第四条 国家对经营性互联网信息服务实行许可制度;对非经营性互联网信息服务实行备案制度。

互联网信息服务管理办法, 2000 年 9 月 20 日

至此,我们熟悉的 ICP 备案走上了台前。而我们现在了解到的各种具体的备案的细节,则是在管理办法内容的延展,如果你对于这些内容感兴趣,可以详细的阅读这个信息管理办法。

在 2005 年 1 月 28 日,在《互联网信息管理办法》之上的细分管理办法《非经营性互联网信息服务备案管理办法》(后称非经营性管理办法)也相应出台,对于非经营性互联网信息服务进行了更加明确的安排和说明。我们所熟悉的备案登记表、省级通管局、网站底部公示备案编号等描述也出现在了非经营性管理办法当中,网站的备案事项,也正式被推行。

备案到底在备案什么?

备案对于很多人来说,最麻烦的不是搞不清楚怎么备案,而是搞不清楚如何处理域名、网站、 服务器、个人之间的关系。而所有的这些关系,其实可以用一个词来说明 —— 「双绑定制度」。

现行的网站备案制度其实是一种“双绑定制度”,一方面将网站和所有人绑定在一起,另一方面,将网站和它的接入商绑定在一起。一旦某个网站出现了问题,则需要能够在第一时间找到网站的所有人和接入商。

阮一峰,《关于网站备案,》,2009年 9 月 9 日

备案并非一个阻拦性措施,而是一个监管性措施(对比经营性互联网信息服务的许可制),希望大家可以积极踊跃的进行互联网信息服务的同时,避免恶劣影响的网站持续提供服务。找到接入商来第一时间拔网线,缩小影响范围;找到所有人来进行相应的处罚。

备案过程中的五个关键角色

当我们搞清楚了备案的核心理念和目的之后,我们重新回过头来看看备案当中的五个关键角色:备案主体省通管局接入服务提供商域名注册商域名管理机构

d2b5ca33bd970f64a6301fa75ae2eb22 6
各关键角色之间的关系

当我们真正开始进行备案时,就会面临三个元素:域名、服务器、所有人。这五个关键角色与三个元素,依次发生关系。

域名管理机构和域名

当我们开始思考建设一个网站时,必然会涉及到域名的问题。而实际上自 2017 年 11 月 30 日颁布的《工业和信息化部关于规范互联网信息服务使用域名的通知》正式实行之后, 并不是每一个域名都能够完成备案。在该通知中,要求互联网域名注册服务机构要在我国进行相应的注册和审批,才能够让对应的后缀进入到备案的流程。如果对应的域名注册服务机构没有在我国进行注册,则对应的域名无法在我国进行备案。

从而,也就出现了现在很多时候我们在购买域名时候需要考虑的一些问题 —— 域名能否完成备案?

目前所有完成了相应注册机构的审批都可以在工业和信息化部域名行业管理信息公示网站找到。如果你需要购买域名且有打算在国内进行备案,则一定记得确认你所购买的域名是否可以在国内完成备案的流程。

此外,如果你感兴趣的话,还可以在这里找到一些你可能从来都没听说过、没见过的后缀,比如.baidu

对于已经进行的域名注册服务机构,因为工信部有了相应的沟通方式,则可以在出现问题后,下发公函,要求对应的服务机构协助调查和配合。而对于没有注册的管理机构,无法完成相应的配合,干脆不予后缀备案。

这也解释了为什么现在你购买的 .io 域名不能备案,因为对应的注册管理机构没有在我国注册,不符合备案的要求。至于你看到的那些有备案号的 .io 域名,则是在相应的通知发布之前完成的备案,故而可以正常使用。

域名注册商和域名

当你选择了一个可以进行备案的域名后缀(比如 .com)之后,就可以进行域名的注册了。域名的注册需要到对应的域名注册商来完成。这个时候,也会涉及到不同的域名注册商可能会影响你的备案流程。

按照新的管理办法,你的域名需要在购买的时候完成实名认证。因此,你如果域名想要备案,则要求你的域名注册商支持进行实名认证。

比如,目前你可以在阿里云(万网)、腾讯云(新网)、西部数据等域名注册服务提供商这里购买域名并进行实名认证,在实名认证完成后,再进行后续的域名备案操作。

相应的,对于域名注册服务机构,也采用了审批和注册的限制,不同的服务商能够支持的域名实名认证也是不同的,企业需要根据「互联网域名注册服务机构审批」来进行提交,等待审批通过后,才能提供相应的域名注册和实名认证服务。

目前通过审批的域名注册服务机构也可以在工业和信息化部域名行业管理信息公示网站找到。

这也解释了为什么你在 Godaddy 、Namesilo、Namecheap 之类的域名注册机构注册的域名无法在国内进行备案,因为他们并没有在国内注册,接入相应的实名认证流程,自然无法为你提供后续的备案服务。

域名和所有人

当你选择好了域名后缀和域名注册服务商,进入了详细的域名注册流程当中时,会涉及到域名的实名认证的环节。

在这个时候你需要注意,备案要求域名实名认证和备案的主体是保持一致的,因此,如果你后续打算以哪个主体(个人或者企业)进行备案,在这里进行实名认证时,就要选择对应的信息来进行实名认证。

你提交信息后,域名注册服务机构(域名注册服务商)就会将你的域名和对应的实名信息提交到工信部。一般 1 ~ 2 个工作日后,你的信息就可以在工信部的内部系统可查,这个时候才能进行下一步的备案。

接入服务提供商和域名

域名购买好了,接下来就是选择接入服务提供商。由于备案实际上是个人与网站、网站与接入服务商进行的「双向绑定」,因此,能够提供备案服务的服务商都是在工信部进行了相应的注册的服务商(需要持有互联网数据中心业务经营许可证)。

在进行备案时,会要求你的域名已经完成了实名认证,然后再提交信息进行相应的备案,这里会要求域名的实名认证和接入服务提供商的实名认证信息保持一致,进行审核。

这也解释了为什么你购买的 DigitalOcean 的服务器无法进行备案,因为 DigitalOcean 并不持有互联网数据中心业务经营许可证。当然, DigitalOcean 的服务器也不需要进行备案。

所有人和通信管理局

当你进行备案时,会让你选择一个备案的所在地,备案的所在地决定了你的网站归属于哪个省份的通信管理局来进行管理。

一般来说,大家会选择身份证所在地。当然,如果按照惯例办法中的说明了,其实你还可以选择你的当前所在地(因为管局在乎的是能否联系到你)。选择了对应的省份,需要你提交相应的信息(需要能够精确到门牌号),基于对应的信息来进行备案。不过需要注意的是,不同的地区可能有所不同,比如基于当前所在地的备案可能会要求你提供居住证来进行备案。

理论上,你可以拥有多个不同的备案号,一个身份证所在地的备案号,和多个居住地所在的备案号

接入服务提供商和通信管理局

当你准备好所有信息,接入服务商帮你进行了基本的审核后,就会将你的信息提交至你备案的对应的通管局。而 ICP 备案本身是备案制度,因此,审批其实大部分情况下都是能够通过的,只是说需要一点时间,不同的省份在备案的审批上所需的时间也不同,根据《非经营性互联网信息服务备案管理办法》的要求,会在 20 个工作日内完成审核。不过某些特定的省份是比较快的(比如上海、浙江,据说是可以 1个工作日完成审批)。

当你完成审批后,就会获得一个备案号,将对应的备案号放置在你的网站底部,并加上前往通管局的链接即可。

拿到备案号之后的注意事项

新增备案网站

当你完成了第一个网站备案和建设后,你可能会需要进行第二个网站的备案和建设,这个时候,你需要注意:

  1. 你的第一个网站的用途应该符合备案时申报的类目(不然可能无法进行第二轮)
  2. 你应当按照规定放置了各项备案号等信息。
  3. 进行第二轮备案时,你的第一个网站应该是可以打开的。后续每新增一个网站,就会要求你之前的网站是可以正常打开的。

这就要求你如果某个域名不再使用,尽快将其关闭,并注销域名和相应的备案。

备案号、域名和接入服务商

当你拿到域名备案号之后,你就可以在你办理备案的接入服务商进行网站的建设和开发工作了。但需要注意的是,你仅能使用当前办理了接入服务商的服务,如果想要使用其他家的服务,则需要再次办理接入,新增一个接入,以使用对应服务商的产品。毕竟,备案的核心诉求是「双向绑定」,绑定用户和网站、网站和接入商。你虽然完成了备案,解决了用户和网站的绑定,但如果你没有完成网站和接入商的绑定(做接入备案),接入商同样会拒绝为你提供服务。

此外,需要注意的是,你的名下应当始终至少有一个网站,对于一个网站都没有的(比如你注销掉了所有的网站)人,在备案系统中被称为「空壳主体」,通管局会定期清理空壳主体,所以为了保住你的主体和备案号,尽量保证你的名下有网站。如果需要迁移服务商,也在完成了新的服务商接入之后,再注销之前服务商的接入。

独特的 CDN

在备案体系中,有一个很独特的产品 —— CDN 。和云主机不同,云主机会要求进行接入后才能使用,而 CDN 则没有相应的要求,你只要完成了备案,无需再进行接入即可使用 CDN 。

CDN 作为一个分布式的产品,因为没办法统一进行接入,故而只做了备案的限制,而不再要求你的域名必须在对应的服务商进行接入。

如果网站出现了问题,可能会怎么处理?

其实前面说了那么多,都是一些知识性的内容,接下来,我们来面对一个真实世界的问题 —— 如果某个网站出现了问题。可能会怎么处理

前面说过,备案本质上是为了能够在出现问题后快速找到你,解决网站中的问题。但真正在执行时,可能会有哪些操作?

  1. 如果有备案,直接联系备案时提供的联系电话(这也是为什么你备案的时候会有一个联系电话和一个备用联系电话),取得联系后,由网站负责人进行整改。
  2. 如果备案电话打不通,则可以联系服务接入商,通过接入商来暂停网站的接入服务,从而让网站无法访问。
  3. 如果服务商接入暂停后,依然可以访问(即你虽然备案了,但你的网站其实在其他服务器),那就通知你的 DNS 解析服务提供商停止解析。
  4. 如果你的 DNS 也是自己解析的,那就可以联系你的域名注册服务提供商,直接停止域名的服务状态。
  5. 如果你甚至域名注册服务商都是自己搞的,那可以联系域名管理机构,停掉你这个域名的使用。
  6. 当上述的所有办法都无法解决问题后,那就直接给你拉入黑名单,成为我们过去常说的被「墙」了的网站。

后记

这篇文章写完了,让我心头一松。我从 2012 年开始建立个人网站,2013 年、2014 年开始备案第一个域名,后续在 2016 年,在网易实习时以接入服务商视角看待了备案,再到在线,备案的政策不断的变化,我自己从某种意义上也经历了备案从松到严的一段历程(虽然没有经过最松的那段时间)。

备案机制从很多从业者的角度来看,觉得是繁文缛节,但换个角度来看,也是为我们的互联网提供了一定的基本保障制度。互联网上的信息良莠不齐,备案虽不能以黑名单的机制保证我们所看的信息“绝对干净“,但却也以白名单的方式给了一些建议。

不过,这篇文章也有遗憾,因为篇幅限制,我未能介绍关于公安备案相关的信息,这部分就留作后续再独立成文来介绍吧。

如何在阿里云上申请免费的 SSL 证书

turned on black Android smartphone

在进行网站建设的时候, SSL 证书是一个可以有效的提升网络数据传输过程安全性的手段。而一般来说,大家可以选择直接购买 SSL 证书或使用 Let’s Encrypt 提供的免费证书。但 Let’s Encrypt 的证书问题在于往往是短期证书(比如三个月),如果要使用在 CDN 上,就会需要经常替换,非常的麻烦。

目前各家云厂商大多都提供了免费的 SSL 证书,方便大家使用,不过有些时候入口比较深,所以大部分人找不到,只能购买付费的证书。写这篇文章就是希望帮助那些希望使用云服务厂商免费证书的同学快速找到入口。

操作步骤

1. 进入阿里云控制台

打开 console.aliyun.com 了,登录进入阿里云控制台。

2. 进入「SSL 证书(应用安全)」页面

在阿里云控制台找到 SSL 证书(应用安全)这个产品,点击进入产品控制台页面。

d2b5ca33bd970f64a6301fa75ae2eb22

3. 点击控制台左侧的 「SSL 证书页面」,进入到 SSL 证书页面

4. 在 SSL 证书页面点击上方 Tab,进入到免费证书 Tab

d2b5ca33bd970f64a6301fa75ae2eb22 1

5. 点击「创建证书」按钮,创建新的空白证书

d2b5ca33bd970f64a6301fa75ae2eb22 2

6. 点击操作栏中的「证书申请」,申请证书

d2b5ca33bd970f64a6301fa75ae2eb22 3

这部分需要注意的是:

  1. 证书绑定域名:阿里云免费证书只支持单个域名。不支持多域名和泛域名。
  2. 域名验证方式:如果你的域名使用了阿里云提供的 DNS 服务,就可以使用自动 DNS 验证方式。如果不是用阿里云的 DNS,就需要手动配置 TXT 记录来进行验证。
  3. 联系人:填写具体的信息,如果证书申请过程中有疑问,会联系这个电话。
  4. CSR 生成方式:你可以自己创建 CSR,也可以让系统生成。如果不是特别关注,使用系统生成就行。

7. 下载证书

当你配置完成后,稍等片刻,证书就会生成成功,此时点击操作栏的「下载」即可下载到证书文件,你根据自己的实际情况,下载对应格式的证书即可。

d2b5ca33bd970f64a6301fa75ae2eb22 4

总结

虽然阿里云的免费证书一年只有 20 个证书的名额,但对于绝大多数人来说,已经足够,在你需要 CDN 或其他更新不方便的情况下,使用云厂商提供的免费一年证书还是挺好的。

在 Pug 中实现类似 get_sidebar() 全局方法

javascript

在开发 WordPress 主题时,我们会用到一些全局方法,来帮助我们快速加载特定的区域的代码。如果我们在设计和开发主题的时候,也可以实现类似的功能,则在开发对应的页面和应用的时候,我们就可以根据自己的需求来进行特定区域的代码的封装。这样我们在进行后续的开发的时候,就可以简化自己的代码,同时还可以按照 WordPress 的规范提前拆分代码,在实际进行项目开发的时候,提升效率。

原理

本次实现主要是基于 Pug 自带的 Mixins 机制来实现在主题中提供自定义的函数,从而实现我们想要的内容。在原理上,和 WordPress 的 get_sidebar 之类的方法不完全一样。所以,在体验上还没有做到像 WordPress 那么方便。

实现方式

1. 创建 Mixins

在项目的根目录下创建一个 mixins 目录,并在其中创建一个 includes.pug 文件和一个 getHeader.pug 文件。

includes.pug 文件中添加如下代码

include getHeader
Code language: PHP (php)

getHeader.pug 文件中添加如下代码

mixin getHeader
    p header

2. 引入 includes.pug

在你的 layouts.pug 文件中加入 includes.pug 的引入。需要注意的是,要加在 doctype 之前。

include ../mixins/includes
doctype html
html
  head
Code language: PHP (php)

3. 在需要的地方调用

当你完成上述的配置后,即可在需要的地方进行调用。

extends layout

block content
  +getHeader
  h1= title
  p hi to #{title}
Code language: PHP (php)

调用的效果如下:

d2b5ca33bd970f64a6301fa75ae2eb22 39

如果你希望实现更加强大的功能(比如参数),可以参考 Pug 官网的 Mixins 页面。

为 Express 项目添加文件变更自动刷新

javascript

我最近在准备开发新的 WordPress 的主题的工具,为了方便自己开发主题,正在准备一个脚手架,本文是开发过程中的经验总结。

白宦成

在进行主题开发的时候,一个很好的体验是来自于变更能够实时生效。过去我们想要实现这样的功能需要自己手动刷新浏览器。但随着前端技术的进步,我们可以借助诸如 browser-sync 这样的工具来完成自动的刷新,从而实现无需手动刷新,代码修改后自动刷新。此外,除了 browser-sync, 你还可以选择 webpack-dev-server

原理分析

此类工具的工作模式是比较简单的,一般而言,会要求你设定一个代理的端口,他会将对应端口的请求进行转发,并在其中加入 BrowserSync 的 JS 文件,从而实现可以无需手动操作来刷新。

d2b5ca33bd970f64a6301fa75ae2eb22 37
browser-sync-client.js

而其刷新的能力则是源自于 BrowserSync 本地提供的 WebSocket Server。嵌入的 JS 文件中加入了 WebSocket 的相关链接代码, 从而实现从 Command Line 当中获取刷新指令,前端则在获取到指令后进行刷新。

d2b5ca33bd970f64a6301fa75ae2eb22 36
WebSocket 请求

具体步骤

1. 安装 BrowserSync

首先在项目中引入 browser-sync ,执行如下命令

npm install browser-sync --save-dev

2. 修改启动文件

我是使用 express generator 生成的项目,需要修改 bin/www 文件,如果你是自己建设的项目,则需要自行修改。

在启动文件中找到监听端口的代码, 并在其回调函数中添加如下代码

# 添加前
server.listen(port)
# 添加后
var bs = require('browser-sync').create();
server.listen(port, () => { 
  bs.init({
    open: false,
    ui: false,
    notify: false,
    proxy: 'localhost:' + port,
    files: ['./views/**', './routes/**'],
    port: 8080
  });
});
Code language: PHP (php)

上述配置中,可以根据你的需要修改 proxy 、files、port 等选项,实现自定义配置。

关于配置的更多信息,你可以参考 https://browsersync.io/docs/options

3. 启动服务,并测试

在命令行中执行 npm start ,在浏览器中打开对应的页面,当可以看到内容后,可以修改文件,等待页面的自动刷新,如果成功刷新了,则表示你已经完成了相关能力的配置。

在开发 Gutenberg 插件时,如何处理防抖?

silver mercedes benz emblem on blue surface

在开发 Gutenberg 插件时,如果你需要对编辑器的内容进行处理,则需要为其加入防抖措施,避免你的函数被频繁调用。

思路

由于编辑器需要高频处理,因此需要采用防抖策略,避免将多次输入视为不同的事件。这里可以使用 WordPress 提供的 useDebounce 来实现控制。需要注意的是,WordPress 自动的 Debounce 函数传入的参数应当采用 useCallback 来进行包裹。

参考代码

import {subscribe} from "@wordpress/data";
import {useDebounce} from  "@wordpress/compose"
import {useCallback} from "@wordpress/element";

const debounced = useDebounce(
        useCallback(() => {
            // do some thing
        }),500
    );
subscribe(debounced);
Code language: JavaScript (javascript)

使用 Github Action 发布 WordPress 插件

black and white penguin toy

WordPress 官网的插件系统采用的是 SVN 来进行管理,如果你希望将自己的插件发布至 WordPress 官网,就必须在本地安装 SVN,不太利于项目的管理。此外,WordPress 官网提供的是一个发布系统,而非版本控制系统(只是将 SVN 用作插件管理,并不是真打算让你每一个变更都提交),因此,你还是需要使用另外的版本控制系统(比如 Git)来管理你的代码。

为了简化发布,你可以采用 GitHub Action 来完成你的插件自动发布,这样你就可以使用 Git 来开发和管理你的插件,并让 Github Action 自动进行插件的发布。

具体操作

1. 配置 Action Secrets

因为你需要使用版本控制来进行发布,以及可能存在的协作开发的场景,处于安全的考虑,你应该将你的 SVN 的信息放置在 Action Secrets 当中。

d2b5ca33bd970f64a6301fa75ae2eb22 34

在仓库当中找到 Secrets – Actions ,新建两个 Secrets:SVN_USERNAMESVN_PASSWORD,配置上你的 SVN 账号信息,稍后将会使用你的这个账号信息来进行插件的发布

2. 创建一个 .distignore 文件

.distignore 文件可以帮助你实现忽略不需要发布到 WordPress 插件系统的文件,这样就让你的插件目录变得更加干净一些。在你的仓库根目录创建一个 .distignore 文件,并填写具体要忽略的文件,即可实现部分发布至 WordPress 插件目录。

以下是我的 Example

node_modules/
.github/
.git/
.distignore
.gitignore
package.json
package-lock.json
yarn.lock

3. 创建 Action 文件

在你的项目的 .github/workflows/ 中创建一个 publish.yml文件,并在其中添加如下代码即可。

name: Publish to WordPress Plugin Directory
on:
  push:
    tags:
    - "*"
jobs:
  tag:
    name: New tag
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Build # 如果没有 NPM 相关构建工作,可以去除这一步。
      run: |
        npm install
        npm run build
    - name: WordPress Plugin Deploy
      uses: 10up/action-wordpress-plugin-deploy@stable
      env:
        SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
        SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
        SLUG: wpstoreapp-spellcheck
Code language: PHP (php)

4. 发布版本

接下来只需要执行如下操作,既可以发布版本

git add .
git commit -m 'feat: release new version'
git tag 1.0.0
git push && git push origin 1.0.0
Code language: JavaScript (javascript)

上述代码中的 1.0.0 即为具体的版本号,可以根据你的诉求进行修改。

总结

在完成了配置后,后续你的插件发布就变得十分简单了:修改本地的 readme.txt 中的版本号,加入 changelog,并修改插件文件头中的版本,即可提交一个 commit ,添加对应的版本的 tag,然后 Push 到 Github 上,由 Github 来进行插件的发布。

而如果你是以公司/团队的形式来管理插件,也可以用类似的方式,这样虽然每个人都不掌握 SVN 的账号密码,但却可以完成插件的发布,十分方便。

在 WordPress 的插件页面添加更多的连接

turned-on monitor

WordPress 的插件页面提供了不少的 Hooks,可以帮助我们实现自定义的插件页面,让用户在启用插件后,第一时间可以看到我们为其提供的功能,从而降低用户使用的成本。

d2b5ca33bd970f64a6301fa75ae2eb22 33

以上图为例,你可以看到,我在左侧的功能区添加了「配置 Token」和「购买额度」;右侧的更多链接区提供了帮助手册和联系我们,帮助用户优化体验,更快的了解如何使用你的插件。

左侧功能区

首先,在左侧功能,WordPress 提供了两个 Filter来实现这个功能:

一个是 plugin_action_links,这个插件传入两个参数,一个是当前插件的链接构成的数组,另一个则是插件的文件路径,你可以根据传入的参数判断当前是使用哪个插件,并通过返回不同的数组,来实现不同的功能。

另外一个则是 plugin_action_links_wpstore-spellcheck/wpstore-spellcheck.php,是在上一个 filter 的基础之上,加入了插件的路径,从而让你可以无需进行判断,直接针对对应插件来完成链接的修改。

以上图效果为例,具体的代码如下

function wpstoreapp_plugin_action_link($links, $file)
{
	if($file == plugin_basename(dirname(__FILE__) . '/wpstore-spellcheck.php')){
		$links[] = '<a href="./options-writing.php">配置 Token</a>';
		$links[] = '<a href="https://api.wpstore.app/plugins/spell-check/pricing" target="_blank">购买额度</a>';
	}
	return $links;
}

add_filter('plugin_action_links', 'wpstoreapp_plugin_action_link', 10, 2);
Code language: PHP (php)

这里我选择的是第一种实现方式,主要的原因是我无法保证用户安装插件一定是采用的是 WordPress 插件目录的安装方式,因此,在这种情况下,第二种带了路径的 filter 的名字是有可能发生变化的。

右侧更多链接区

右侧更多链接区可以用来承载联系我们、帮助文档等入口,从而实现用户在使用时,可以快速找到相应的辅助资料,从而获得更好的体验。

从实现的逻辑上来看,和在左侧功能区添加链接的基本逻辑是一样的,需要判断当前插件是否是目标插件,如果是,则可以根据需要添加功能。

add_filter( 'plugin_row_meta', 'wpstoreapp_plugin_row_meta', 10, 2 );

function wpstoreapp_plugin_row_meta( $links, $file ) {
	if ( plugin_basename( __FILE__ ) == $file ) {
		$row_meta = array(
			'docs'    => '<a href="https://www.wpstore.app/?p=291">帮助手册</a>',
			'contact'    => '<a href="mailto:hi@wpstore.app">联系我们</a>'
		);

		return array_merge( $links, $row_meta );
	}
	return (array) $links;
}
Code language: PHP (php)

总结

WordPress 为我们提供了方便的 Hook,可以让我们可以自定义我们想要的功能,借助这些功能来降低你的用户的 Landing 成本,从而实现更好的服务用户,是一个不错的选择。