为 Next.js 加上 Git Commit 版本号

text

在开发服务端应用的时候,由于服务端应用本身的特性,其实是没有一个明确的版本的概念。毕竟不需要专门下载,理论上每次都是最新的,所以也没有版本的概念。

但在实际开发调试过程中,我们又的确需要关注版本的概念,因为会影响具体的表现形态,所以就需要有一个前后端协调的版本号概念,来帮助我们更好的定位问题,避免前后端之间的扯皮。

一个比较好的思路是,虽然服务端没有版本号概念,但大部分时候会有一个对应的 Commit ID(毕竟现在开发项目完全不用版本控制工具的还是挺少见的)。所以,你可以选择将 Commit ID 作为版本号,进行输出,从而让协作者知道当前线上跑的版本,便于 debug。

在具体实现时,有两种方式:

1. 将 Commit ID 放在 Header 里

我自己平时会把 Vercel 和 Next.js 提供的 API Route 作为一个简单的 Serverless FaaS 环境来使用,因此一个诉求便是在 API Route 当中返回具体的 Commit ID。而为了避免对代码的侵入,将其放在 Response Header 当中是比较合适的。

d2b5ca33bd970f64a6301fa75ae2eb22 2
添加完成的效果。

而如果你希望和我一样,达成对特定路由下的返回结果添加特定的 Header(比如上面截图中 x-build-sha 就是我添加的 Commit ID 的 Header),则需要借助于 Next.js 提供的自定义 Header 能力

通过在 next.config.js 当中的 header 属性中添加具体的配置,来实现对特定的路径下添加自定义 Header。

module.exports = {
  async headers() {
    return [
      {
        source: '/about',
        headers: [
          {
            key: 'x-custom-header',
            value: 'my custom header value',
          },
          {
            key: 'x-another-custom-header',
            value: 'my other custom header value',
          },
        ],
      },
    ];
  },
};
Code language: JavaScript (javascript)

这里面比较关键的是 source 字段,这个字段定义了究竟哪些路由下会返回特定的 Header。比如上面的这段配置就是只给 /about 添加具体的 Header。你可以使用 /:path* 来匹配所有路由,从而实现给所有路由都添加上具体的 Header。

以我为例,我在线上跑的配置实际上是下面这段配置:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [{ key: 'X-Build-SHA', value: process.env.VERCEL_GIT_COMMIT_SHA }]
      }
    ];
  }
}

module.exports = nextConfig
Code language: JavaScript (javascript)

在上面这段配置中,我给所有的路径都配置了一个 x-build-sha 的 header ,并从进程的变量中提取出 VERCEL_GIT_COMMIT_SHA 变量(这个变量在 Vercel 的部署环境中指向具体的 Commit ID)的值,将其返回。

2. 将 Commit ID 放在 UI 里

除了在 Header 中返回,如果你是需要去 Debug UI 的话,版本号同样重要,这个时候,你可以选择将 Commit ID 放在界面上,从而实现快速找到 Commit ID。

在 Vercel 部署的 Next.js 上,有一批 Next.js 框架所属的环境变量, 可以直接在 UI 当中引用(上面的 VERCEL_GIT_COMMIT_SHA 是不能在 UI 中直接引用的)。

只需要在特定的位置,加入SHA: {process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA} ,就可以展示具体的 Commit ID。

(除了 Next.js,其他框架也有类似的框架变量可以使用,你可以根据自己的需求来选择)。

总结

在服务端 Debug 时,将你的 Commit ID 以某种方式返回可以有效的帮助快速定位问题,试着给你的 Next.js 添加上这个 Commit ID,来加速你的问题排查吧~

使用 systemd 后台值守运行 Bark

green and black digital device

什么是 Bark?

Bark 是由 Fin 开源的一个向 iOS 设备推送通知的服务,Bark 支持自行部署服务端,从而实现消息的推送通过自己的服务器进行,避免与官方的服务共享,提升推送时效。

服务端源码:Finb/bark-server: Backend of Bark (github.com)

客户端源码:Finb/Bark: Bark is an iOS App which allows you to push customed notifications to your iPhone (github.com)

Bark 如何部署?

Bark 的部署非常简单,直接下载官方的 release 文件即可。

下载完成后,你可以执行如下命令来进行测试

bark-server -addr 0.0.0.0:8080 -data ./bark-data

执行成功后,你会看到如下的界面,此时你可以访问 127.0.0.1/ping 来验证你的 Bark 的部署

1jj0p

使用 Systemd 进行值守

验证成功后,接下来就可以设定值守运行了

1. 将 bark server 移动到一个固定目录

首先,你需要将 bark server 移动到一个固定的目录,方便后续执行。

mv bark-server /usr/local/sbin/bark-server

2. 创建 Bark Service

创建 /etc/systemd/system/bark.service 文件,并添加如下内容

[Unit]
Description=Bark Server
[Service]
ExecStart=/usr/local/sbin/bark-server -addr 0.0.0.0:8080 -data /usr/local/bark-data
[Install]
WantedBy=multi-user.target
Code language: JavaScript (javascript)

创建完成后,你可以执行 systemctl status bark 来查看 bark server 的状态。

7jwil

然后,你就可以使用 systemctl 来控制bark 了

  • 启动服务 systemctl start bark
  • 停止服务 systemctl stop bark
  • 查看服务状态 systemctl status bark

如何用 CURL 看证书信息

green and black digital device

在进行证书故障排查的时候,难免要查看证书信息。不过目前的浏览器在设计上,查看证书详情变得困难很多,因此,你可以借助 CURL 来查看证书信息。

具体的命令

 curl --insecure -v https://域名 2>&1 | awk 'BEGIN { cert=0 } /^\* SSL connection/ { cert=1 } /^\*/ { if (cert) print }'
Code language: JavaScript (javascript)

返回结果如下,其中包含了域名证书当中的过期时间、申请者等核心信息,方便你进行排查。

➜  ~  curl --insecure -v https://www.ixiqin.com 2>&1 | awk 'BEGIN { cert=0 } /^\* SSL connection/ { cert=1 } /^\*/ { if (cert) print }'
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=www.ixiqin.com
*  start date: Aug 15 00:00:00 2021 GMT
*  expire date: Nov 13 23:59:59 2021 GMT
*  issuer: C=AT; O=ZeroSSL; CN=ZeroSSL RSA Domain Secure Site CA
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fe096810a00)
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* Connection #0 to host www.ixiqin.com left intact
* Closing connection 0
Code language: PHP (php)

如何实现子域名应用?

red and white heart illustration

多域名应用的心思是从 WordPress.com 的时候起来的,当时一直在想,如何实现这种多域名的应用?

最近看到了一个实现,算是解了心头的疑惑。

如果不涉及 SSL ,子域名应用的实现并不算复杂,可以简单的通过将用户请求进行 rewirte 转发的方式,来实现对请求的转发。

举个例子,比如将 x.example.com 转发到 www.example.com/pages/x 中,这样在应用中就无需单独对于多域名编写代码,只需要从 Path 中提取前缀,并进行数据库查询,将数据结果返回回去就好。

如果涉及到 SSL,子域名应用的实现相对复杂一些,涉及到了 SSL 证书的管理。不过也有实现简单的方式,那就是购买一个泛域名证书,这样 Nginx 会通过泛域名证书提供服务,因此,应用程序在处理上和上面的逻辑一样,只要将请求转发就好了。

如果涉及到子域名绑定,则相对麻烦一些,需要能够编程式的操作 Nginx 的代码文件,不过也还好,你可以在应用的目录下动态的生成 Nginx 的配置文件,并在默认的配置文件中 include 动态生成的目录,从而避免文件生成后的管理成本太高。此外,需要借助 execl ,执行命令对 Nginx 执行检查和重启的操作。

此外,也可以考虑直接使用 default_server ,从而直接将所有未识别的情况转发到某个特定的路径。不过需要在应用中添加请求判断,对于无法绑定的域名,提供一个特定的反馈,引导用户进行绑定。

总结

如果你的应用不涉及到 SSL,也不涉及到域名绑定,则十分简单,直接使用 Nginx 转发请求即可;但如果涉及到了 SSL,则需要考虑泛域名证书,从而降低编程的成本;如果涉及到了域名绑定,则需要为应用程序新增对 Nginx 操作的能力,从而降低应用的管理和研发成本。

Continue reading

离用户近一点,再近一点

在现代的工业体系下,任何工作都被拆分为流水线上的一环,如今的互联网行业更是从用户那里知道他们想要什么再到实际做出来,有足足六七个环节。

006tNc79ly1fzwukjnuwcj31220ggwey

作为一个有写代码爱好的人来说,能选择的余地不多,唯有「后端工程师」和「前端工程师」,在过去的很长时间,我基本上呆在后端的领域,去做了很多后端相关的开发,自己也在后端方面有了更多的认识。

在新的 2019 年,我将会尝试让自己转向,成为一个前端工程师。接下来,我来说一说我这个选择的背后逻辑。

员工的价值到底由什么决定?

白子:离客户越近,其价值就越大。

提到择业,就避不开两个话题,企业的需求和员工的价值。一般来说,我们认为,员工的价值由他为企业带来的价值所决定

这句话没错,那么,员工如何为企业带来价值?

员工可以帮助企业创造更好的产品,但是,这是价值么?

更好的产品本身并不是价值,其所带来的用户、客户才是真正的价值

员工本身并不让企业盈利,相反,企业需要支付费用给员工。而客户则是支付费用给公司,帮助企业盈利。

从这个角度来看,离客户越近的人,越能产生价值,这也就是为什么我们会经常看到一个企业里,销售是赚钱最多的人,因为他们离客户最近,能够给企业带来实打实的价值。

技术背后的陷阱

白子:技术本身就是螺丝钉,只研技术,不过是一个螺丝钉,变成一个更粗的螺丝钉。

关注技术本身有没有坏处?当然没有,作为一个开发者,追求技术的卓越是应有的义务。但是,从企业的角度来说,只关注技术本身,意味着你的价值会不断降低。

技术再强,也是可以找到替代者的,区别仅仅是愿不愿意花那么多钱罢了。业务理解的深度,却是其他人无法轻易替代的。江山代有才人出,各领风骚数百年,技术迭代速度非常快,总会有新人出来,比你更加擅长技术。

为什么是前端不是后端

白子:如今的前端更加接近业务本身,更具备价值

随着现代软件产品的高度流水线化,我们推崇的前后端分离、RESTFul API、GraphQL 让后端的工作越来越轻松,可以花费更多的精力投放在技术深度的探索,去研究更加深层次的优化问题,而不需要花费更多的心思在业务逻辑上去。

同样的,前端不得不承担起业务流程的开发,工作量大大加大。虽然有各种各样的组件库帮助前端优化了具体布局、界面上面的工作,但业务流程本身的复杂度并不会因为组件库的引入而简化

在这种强前端重后端的模式下,前端承担了原本是后端的工作,让后端不再需要去理解业务逻辑,更加关注技术本身的内容就可以了。离业务越来越远,使得后端的话语权越来越小。

游刃有余的前端

前端工程师本身负责的是客户可以看见的内容,这使得他们相比于后端工程师,有着更多的职业选择

他们了解用户交互体验,可以从开发转换成为用户研究

他们了解用户使用方法,可以从开发转换成为销售

他们了解用户使用路径,可以从开发转换成为产品经理

而后端,由于专精于技术,其职业选择,也不过是从一门技术,转为另外一门技术罢了。

Django Models 参数

我创建model必加的字段


class Category(models.Model):
    pub_date = models.DateTimeField('发布日期',auto_now_add=True)
    update_date = models.DateTimeField('更新日期',auto_now=True)
    sort = models.IntegerField("序号",default=99,help_text="序号越小越靠前")
    def __unicode__(self):
        return self.title
    def __str__(self):
        return self.title
    class Meta:
        verbose_name = '目录'
        verbose_name_plural = '0-目录'
        ordering = ['sort']  # 按照哪个栏目排序
        get_latest_by = 'pub_date'

创建了新的 Django Project 后需要设置的

black and yellow box on white table
  1. 创建 Env
    virtualenv venv
  2. 生成 git 目录
    git init
  3. 加入gitignore
    # Byte-compiled / optimized / DLL files
    __pycache__/
    *.py[cod]
    # C extensions
    *.so
    # Distribution / packaging
    bin/
    build/
    develop-eggs/
    dist/
    eggs/
    lib/
    lib64/
    parts/
    sdist/
    var/
    venv/
    collectstatic/
    *.egg-info/
    .installed.cfg
    *.egg
    # Installer logs
    pip-log.txt
    pip-delete-this-directory.txt
    # Unit test / coverage reports
    .tox/
    .coverage
    .cache
    nosetests.xml
    coverage.xml
    # Translations
    *.mo
    # Mr Developer
    .mr.developer.cfg
    .project
    .pydevproject
    # Rope
    .ropeproject
    # Django stuff:
    *.log
    *.pot
    # Sphinx documentation
    docs/_build/
    # editor
    .vscode
    # database
    db.sqlite3
  4. 修改中文
    LANGUAGE_CODE = 'zh-hans'
    TIME_ZONE = 'Asia/Shanghai'
  5. 设置静态文件目录
    STATIC_ROOT = 'collectstatic'
  6. Database
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql', #数据库引擎
            'NAME': 'user_im',                       #数据库名
            'USER': 'root',                       #用户名
            'PASSWORD': '',                   #密码
            'HOST': '',                           #数据库主机,默认为localhost
            'PORT': '',                           #数据库端口,MySQL默认为3306
            'OPTIONS': {
                'autocommit': True,
            },
        }
    }