作者归档:白宦成

关于白宦成

独立开发者, 自由职业者, 写作者

APILetter

APILetter S1E5 如何设计一个符合 RESTFul 风格的批量操作的 OpenAPI 接口?

批量创建、批量更新、批量删除

批量获取说完了,接下来我们来聊聊批量更新,实际上批量更新、批量创建虽然有场景,但也不多,在这种场景下,我们已经很难像批量获取那样,在原有资源上进行操作,而是需要借助批量资源来实现批量操作。

以用户资源(User)为例,当我们需要对其进行批量创建、删除、更新时,我们需要创建一个批量资源 BulkUser,并通过对 BulkUser 操作,来创建用户。Bulk User 本质上是将请求的多个资源转换为了异步的任务,在发起后,开发者可以在任务结果中查询具体的值来使用。

如何理解异步的任务?
这里异步的任务更多是一种设计表现,并不强制要求一定异步。异步的表现设计和相关的接口实现,是为了给后续留出纵向扩展的空间。既 无论是否行为是否真实异步,都需要在返回结果中返回任务 ID & 任务状态,以便于开发者自行实现异步处理的逻辑。

{
    "code":0,
    "data":{
       "job":{          "id":"123",          "status":"ok"        },
        "results":[
            {
            ...
            }
        ]
    }
}
Code language: JavaScript (javascript)

批量创建

批量创建用户的操作和创建单个用户的操作是比较接近的,主要差异点在于 Path 上有区别,且传递参数时,会传递多个资源的属性。

批量创建用户

# request
POST /bulk_users/

{
    "users":[
        {
            // user1
            ...
        },
        {
            // user2
            ...
        }
    ]
}
# response
HTTP/1.1 200 OK
{
    "code":0,
    "data":[
       {
            // user1
            ...
        },
        {
            // user2
            ...
        }
    ]
}
Code language: PHP (php)

批量更新用户

批量更新时,你已经知道了你需要更新的资源的 ID,因此,可以这样设计的你的接口:

#request
PUT /bulk_users?id[]=1&id[]=2

{
    "gender":"other"
}

# response
HTTP/1.1 204 No Content
{
 "code":0,
    "data":[
       {
            // id=1
            ...
        },
        {
            // id=2
            ...
        }
    ]
}
Code language: PHP (php)

批量删除

有了上面的几个例子,批量删除就比较好定义了。就像这样:

#request
DELETE /bulk_users?id[]=1&id[]=2

# response
HTTP/1.1 200 OK
{
 "code":0,
    "data":[
       {
            // id=1
           "status":"deleted"
        },
        {
            // id=2
            "status":"deleted"
        }
    ]
}
Code language: PHP (php)

总结

批量操作在获取场景,可以考虑通过 List + Filter 的方式,或搜索的方式来实现一套更加标准的搜索接口,而规避提供定制化的自定义接口。从规范的视角,两者都是符合规范的,也可以都对用户提供,并不互斥。而对于没办法复用的创建、更新、删除,则可以考虑使用创建异步任务的方式,来实现批量操作,给开发者一个明确的异步预期,让开发者可以自行查询业务的实现方式。

d2b5ca33bd970f64a6301fa75ae2eb22

云南之旅流水账

云南之旅玩的非常的尽兴,6.17 ~ 6.25 ,全程共计 9 天,玩了昆明、大理、丽江、泸沽湖四个不同的地方,几乎每个地方待了 2 天左右,还是有非常多值得去和值得纪念的东西。不过,由于实在太多了,所以只能用流水账的方式来记录。在流水帐当中,着重说一些大面上的事情,而具体每个地方的更加详细的描述,则通过单独为每一个地方写一篇文章来说。

不过,为了方便你了解我的行程(抄作业),我在 Google Map 上准备了一份地图,方便你了解我去过的地方。如果你感兴趣的话,可以点击这个链接,看到我去的每一个地方,以及相应的评价。

行程总览(Overview)

日期地点描述
6.17天津,昆明,遵义天津飞昆明,遵义茅台机场中转; 落地后入住酒店,在昆明逛了南强街和南屏步行街
6.18昆明,大理早上去滇池,下午从昆明站出发前往大理,到大理后取车前往酒店入住后去大理古城.
6.19大理环洱海,期间去了喜洲古镇、大理圣托里尼
6.20大理,丽江逛崇圣寺三塔、坐感统索道上苍山,晚上还车并从大理坐车前往丽江,晚上住在丽江古城附近。
6.21丽江逛丽江古城、拍照、吃纳西族黑山羊火锅。
6.22丽江,泸沽湖吃过桥米线,从丽江出发去泸沽湖,晚上吃摩梭族烤鱼。
6.23泸沽湖,大理环泸沽湖,吃摩梭族烤鸡,下午开车回大理,并在大理古城拍照
6.24大理,昆明早上在大理吃过早餐,就开车出发去昆明,下午入住斗南花市旁的全季酒店,晚上逛斗南花市。
6.25昆明上午逛云南省博物馆,下午在翠湖公园溜达后,前往昆明长水国际机场。
9 天旅途的 Overview

感受

1. 云南的气候宜人

云南的气候是这次我体会最为深刻的。我在云南旅行的时候,恰逢天津正是高温的时刻,相比之下,云南的最高温不过 30 度简直是太过凉爽。

不过,虽然最高温不过 30 度,但在中午时刻,依然是比较热的,只是没有像北方是“烤”的感觉。而云南的湿度也不高,也不会有广东的“蒸”的感觉。

从气候上来讲,我觉得云南是比海南广东(太蒸),华北平原(太烤)要更好的。太宜居了。

d2b5ca33bd970f64a6301fa75ae2eb22 1
旅行期间天津的天气, 来源 2345
d2b5ca33bd970f64a6301fa75ae2eb22 2
旅行期间昆明的天气,来源 2345

不过,美中不足的是云南的紫外线强度也是太强了。云南 9 天,基本上每天都会喷安耐晒,整个旅程下来,我的小臂/脸上还是黑了一个色号。再去云南,我觉得我可能会直接选择穿防晒衣,物理防晒,最为靠谱。

2. 云南的风景美如画

云南的风光对我来说,可以比肩 19 年在西藏出差时的西藏风景。

蓝天、白云、青草地,还有洱海、泸沽湖这样的水,非常的舒服了。下面贴几张图,让大家感受一下(以下照片均出自原相机,无滤镜,但拍摄设备有 iPhone 13 和 Pixel 6):

d2b5ca33bd970f64a6301fa75ae2eb22 3
从昆明机场到塘子巷时地铁上拍的天和飞机
d2b5ca33bd970f64a6301fa75ae2eb22 4
昆明机场廊桥上拍的天空和机场
d2b5ca33bd970f64a6301fa75ae2eb22 5
滇池风景
d2b5ca33bd970f64a6301fa75ae2eb22 6
滇池水上风景
d2b5ca33bd970f64a6301fa75ae2eb22 7
洱海边拍的苍山
d2b5ca33bd970f64a6301fa75ae2eb22 8
洱海边
d2b5ca33bd970f64a6301fa75ae2eb22 9
泸沽湖夜景(晚上八点左右)
d2b5ca33bd970f64a6301fa75ae2eb22 10
泸沽湖草海
d2b5ca33bd970f64a6301fa75ae2eb22 11
泸沽湖俯瞰
d2b5ca33bd970f64a6301fa75ae2eb22 12
泸沽湖里格半岛

如此美景,我最大的感受就是,悔不该当初,我就应该带上相机来的!这次出行为了方便,我只带了拍立得和手机,所以拍不出我所看到的美景,再来我要带上我的6400、广角镜、长焦镜,把这些美景都拍下来!

3. 云南的美食很特别

这次去云南,赶上雨季推迟,所以没能吃当地的野生菌(我很想试试见手青,据说是云南的顶尖美味)。但也还是吃到了一些好吃的/ 特别的。

比如:在喜洲古镇吃到了炸蝎子🦂。

d2b5ca33bd970f64a6301fa75ae2eb22 13

比如,云南的粑粑,感觉就是各种不同的馅饼。

d2b5ca33bd970f64a6301fa75ae2eb22 14

也吃到了云南必吃的鲜花饼,现烤的是真的太好吃了。

d2b5ca33bd970f64a6301fa75ae2eb22 15

傣族风情手抓饭也少不了

d2b5ca33bd970f64a6301fa75ae2eb22 16

总结

云南这次的旅行给我的感受还是很深刻的,先简单总结三点。更细节的,后面一篇篇来。

可以肯定的是,云南,我还会再去的。

d2b5ca33bd970f64a6301fa75ae2eb22 34

被滥用的云南十八怪

这次去云南,看到了各种各样奇奇怪怪的“云南十八怪”,让我不得不来吐槽一把。

在没去云南之前,我就知道云南有个“云南十八怪”,在解释云南各种和中原地区差异比较大的生活习惯,但说实话,一直没怎么了解过具体的细节,因为不熟悉。

根据百度百科,云南十八怪是这样的:

你说奇怪不奇怪,云南就有十八怪。
四个竹鼠一麻袋,蚕豆花生数着卖;
袖珍小马多能耐,背着娃娃再恋爱;
四季衣服同穿戴,常年能出好瓜菜;
摘下草帽当锅盖,三个蚊子一盘菜;
石头长在云天外,这边下雨那边晒;
鸡蛋用草串着卖,火车没有汽车快;
小和尚可谈恋爱,有话不说歌舞代;
蚂蚱当作下酒菜,竹筒当作水烟袋;
鲜花四季开不败,脚趾常年露在外。

百度百科

但我在云南当地,见到了各种奇奇怪怪的十八怪,甚至炒酸奶也被编进了十八怪:“酸奶炒着卖”

d2b5ca33bd970f64a6301fa75ae2eb22 33
d2b5ca33bd970f64a6301fa75ae2eb22 34
d2b5ca33bd970f64a6301fa75ae2eb22 35

person holding sticky note

在 Render.com 上部署 Django 4.2

最近在写 Linux 中国的翻译工具的时候,后端我使用的是 Django,版本则选择了 Django 4.2,Python 3.11。在部署 Django 的时候,我选择使用 Render.com 来部署。 不过,在部署的时候,我遇到了一些问题,Render 官方提供的 Getting Started with Django on Render 会部署错误,所以有了今天这篇文章, 告诉大家如何把最新的 Django 4.2 部署到 Render 上。

初始化项目

Render 没有使用 pip,而是使用 Poetry 来管理 Django 项目的,因此,你需要使用 Poetry 来完成项目的初始化。

poetry init #初始化 Poetry 的 配置文件
poetry add django gunicorn # 添加依赖 Django 和 gunicorn
poetry run django-admin startproject linuxondjango .
Code language: PHP (php)

初始化项目基本上就是用 Poetry 替代 pip ,这里没有需要针对 Render 特化的部分,就不做过多的介绍。

编写逻辑代码

当你完成了项目的初始化之后,可以编写你自己的业务逻辑代码,这部分不再多讲,可以正常开发使用。

配置项目以支持 Render 的服务端环境。

1. 从环境变量中读取 Secret Key

Django 使用 Secret Key 作为 Session 加密等一些加密场景的 Salt 和 Seed,所以在 Django Admin 创建项目时,会默认生成一个 Session。不过出于安全考虑,最好不要将其放在代码中,而是在服务端生成后,通过环境变量来存储,避免代码泄露后导致的 session 被解密。

你需要在 settings.py 中,添加如下代码,来替代默认的 key。

import os
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY', default='your secret key')
Code language: PHP (php)

2. 在环境变量中读取 Debug 配置

Render 会自动配置一些环境变量,因此,你可以直接通过判断当前环境上下文来确认当前是否是在 Render 的服务端,如果不在,则配置 Debug 为 True,来解决线上不使用 Debug 模式的需求。

DEBUG = 'RENDER' not in os.environ
Code language: JavaScript (javascript)

3. 从环境变量中读取可用域名

Django 是有域名配置的,非配置域名,无法访问当前应用,因此,你需要在 Render 当中读取域名,来确保可以正常访问。当然,如果你自己配置了自己的域名,也可以直接手动写在 ALLOWED_HOSTS 当中。

ALLOWED_HOSTS = []

RENDER_EXTERNAL_HOSTNAME = os.environ.get("RENDER_EXTERNAL_HOSTNAME")
if RENDER_EXTERNAL_HOSTNAME:
    ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME)

Code language: JavaScript (javascript)

配置 render.yml 来支持 Render BluePrint

你可以直接复制下面的内容,来作为你的项目的启动配置。其中 build.sh 为构建项目的配置。

build.sh

build.sh 当中最重要的是重新安装 Poetry,因为我使用的是 Python 3.11.4, 和 Render 默认的 Python 3.7 不匹配,所以没办法直接用默认的 Poetry,需要自动手动升级 Poetry。

#!/usr/bin/env bash
# exit on error
set -o errexit

pip install --upgrade pip; pip install poetry;  # 重新安装一下最新的 Poetry,因为默认的 Poetry 的版本比较低。
poetry install

python manage.py collectstatic --no-input
python manage.py migrate
Code language: PHP (php)

render.yml

Render 当中,最重要的是 startCommandPYTHON_VERSION ,startCommand 这里是我使用 gunicorn 来启动 Django 应用,而 PYTHON_VERSION 则是用来设定具体的 Python 版本,这里我根据我自己的需求,选择了 Python 3.11.4。

databases:
  - name: linuxondjango-db
    databaseName: mysite
    user: mysite
    plan: free

services:
  - type: web
    name: linuxondjango
    plan: free
    runtime: python
    buildCommand: "./build.sh"
    startCommand: "gunicorn linuxondjango.wsgi:application"
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: linuxondjango-db
          property: connectionString
      - key: SECRET_KEY
        generateValue: true
      - key: WEB_CONCURRENCY
        value: 4
      - key: PYTHON_VERSION # 这里的 python version 是用来指定 Python 版本的,比如这里我用的是 3.11.4。
        value: 3.11.4
Code language: PHP (php)

总结

Render 的教程总体来说没啥大问题,但是在一些小的点上,需要你自己简单 Hack 一下,比如需要自己升级一下 Poetry、设定 Python 版本。如果你也在用高版本的 Django & Render,希望这篇文章 可以帮到你。

flat screen monitor

如何解决 Kindle 在 M1 系列设备上无法访问的问题

问题

在使用 M1 的时候,我遇到一个很麻烦的问题是 M1 无法识别出我的 Kindle系统无法自动加载 M1 设备,这导致习惯于导入标注并使用 Klib 管理的我来说,等于用 Kindle 的功能不齐全了。

使用 macOS 自带的系统工具,也可以看到系统 Kindle 的磁盘,但无法加载。

d2b5ca33bd970f64a6301fa75ae2eb22 29
磁盘工具的展示

如果使用磁盘工具尝试加载,也会报错 com.apple.DiskManagement.disenter错误-119930872

d2b5ca33bd970f64a6301fa75ae2eb22 30
报错的提示

无法使用系统工具加载。

解决方案

在参考了 jakevin 的分享后,我使用如下方式来解决我的 Kindle 挂载问题。

查询外置设备

执行如下命令,可以使用系统自带的 diskutil 查看目前有哪些磁盘。我在这里补充了 grep,来筛选出只有外置磁盘的设备。

diskutil list | grep external -A2
Code language: PHP (php)
d2b5ca33bd970f64a6301fa75ae2eb22 31

手动挂载设备

执行如下命令,来手动挂载 Kindle。

sudo mkdir /Volumes/Kindle # 创建一个新的挂载点,挂载 Kindle
sudo mount -t msdos /dev/disk4 /Volumes/Kindle/ # 使用 mount 命令,挂载 /dev/disk4(你根据需要换成你自己的设备。)
Code language: PHP (php)

一般来说, Kindle 的默认格式化是 fat32 格式,所以用上面的命令就行,但如果你的 Kindle 是 ex-fat 格式,则可以使用如下命令挂载。

sudo /sbin/mount_exfat /dev/disk4 /Volumes/Kindle/ #这里使用的是 mount_exfat。
Code language: PHP (php)

如此操作,便可以让 M1 识别 Kindle 了。

d2b5ca33bd970f64a6301fa75ae2eb22 32
9a1f326b911de6c1629837f3b57551e5 1

给你的 console.log 添加一些特定的输出

在写 Node.js 代码时,常常会使用 console.log 来输出内容,以便于调试。但默认的 console.log 只能标准的输出,在很多需要上下文 debug 的时候,可能信息是不足的。除了使用 debugger 以外,你还可以试着改造 console.log

在你的 index.js 顶部添加如下代码,即可实现在使用 console.log 时自动在前面加上时间信息。当然,你也可以实现自己需要的上下文,比如当前的文件、当前的行数等。

console.log = (function() {
  var console_log = console.log;
  return function() {
    var args = [];
    args.push(`${new Date().toLocaleString()}` + ' -> ');
    for(var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    console_log.apply(console, args);
  };
})();
Code language: JavaScript (javascript)

这个函数的逻辑不复杂,对 console.log 进行了覆盖,写如了新的函数,并通过 arguments 将开发者传入的参数重新打印,以确保不丢失开发者传入的参数。

d2b5ca33bd970f64a6301fa75ae2eb22 20

《哆啦A梦:大雄与天空的理想乡》观后感

我看的动漫不多,哆啦 A 梦、火影、中华小当家。哆啦 A 梦出了大电影,也确实有空,就必然要去看看的。

以下内容包含剧透,如你尚未看此影片且准备去看,建议关掉当前页面,后续看完再回来看本文。

d2b5ca33bd970f64a6301fa75ae2eb22 20

这部电影,如果说让我印象深刻的,大概是以下几点:

回环、价值观

回环

看完整部电影后,我觉得设计最巧妙的便是一开始的蓝色甲虫。在电影的开始,甲虫的出现显得略显无用,似乎也没有在推动剧情的发展。

然而当你看到最后,看到蓝色甲虫竟成了哆啦 A 梦重回人间的锚点,也就不得不感叹这个伏笔有意思,前面蓝色甲虫直奔大雄而去也就有了原因。

挺好。

价值观单一是问题

在普拉多普亚,所有人都是听从三贤人的安排,自然而然,也就要遵循三贤人的价值观。然而,当整个普拉多普亚都是三贤人的价值观的时候,任何违反三贤人的价值观的都成为了错误,甚至要被三贤人的价值观重新洗脑。

如果三贤人真的是贤人,倒也罢了。但三贤人并不是真正意义上的贤人,也不存在绝对意义上的贤人。所以,价值观单一成了问题。

多彩多元的价值观,才能让我们更加开心。

做自己

和上一条接近,如果说,放在自顶向下的角度来看,价值观单一是问题,那么自底向上的便是要做自己,这个自己,是遵从内心的自己,而不是别人告诉你的自己(当然,找到自己的内心是一件很难的事情)。

做自己,才能真正达到完美。如果不是做自己,不过是在和别人眼中的自己做磨合,终究是会磨合的血肉模糊的。

总结

总体来说,可以看,算是合家欢电影,带小朋友去看也没啥问题。不会出现一些奇奇怪怪的事情,影响小朋友的价值观。我这种老朋友,就是去怀念一下自己小时候的哆啦 A 梦罢了。

linux terminal

如何将 Zed 编辑器设置为你的命令行默认编辑器

我最近在使用 Zed 作为我的主要编辑器,在编辑一些命令行文件时,也会使用 Zed 来编辑。但有些时候,一些应用程序的命令会自动调用默认的编辑器,这个时候会默认使用 nano 或者 vim ,而不是 Zed,体验略差。所以我希望将命令行默认的编辑器修改为 Zed 编辑器。

如果需要将 Zed 作为自己的命令行编辑器,首先需要确认 Zed 是否支持等待模式(wait model),在命令行中执行 zed --help,可以看到 Zed 是支持等待模式的。接下来就可以进行后续的步骤来进行测试了。

d2b5ca33bd970f64a6301fa75ae2eb22 3

这里为是在写 Rails 时用到的,因此,我继续使用 rails credentials 来测试。执行如下命令来确认该命令是否可用。

EDITOR="zed --wait" rails credentials:edit
Code language: JavaScript (javascript)

执行后,可以正常唤起,则说明整个链路已经通畅,接下来只需要将其配置在系统的默认环境变量里即可,将如下代码放在 .zshrc 中即可。

export EDITOR="zed --wait"
Code language: JavaScript (javascript)

其他编辑器的命令参考

# Sublime Text
export EDITOR="subl --wait"
# VSCode
export EDITOR="code --wait"
Code language: PHP (php)
analog watch at 1 00

天津车检记录

买车记录里,我说过,我目前开的车是一辆 2017 年的雪铁龙 C3 XR,按照最新的汽车年检的规定,刚好满 6 年,需要进行年检,于是我又快速体验了一次年检。

如何进行年检?

年检有两种方式

  • 一种是前置预约的检查,可以在 12306 上预约(或者使用平安车主的代检服务),这种往往检查的速度比较快,虽然也要排队,但是排队的长度比较短,只有几辆车。
  • 另一种是直接去检测机构检查,就需要根据实际的情况来排队,运气不好的话,排队好几个小时是很正常的。

如果你要预约,直接在 12306 App 上找到【机动车检验预约】,就可以预约车辆的年检了。

d2b5ca33bd970f64a6301fa75ae2eb22 25

在预约时,你需要选择时间和地点,并准备抵达现场进行预约检查。到达预约检测地点的时候,一定要问一下哪里是 12306 预约通道,不要和大量的未预约车辆一起排队!避免你预约了,但走的是常规排队通道,浪费时间。

d2b5ca33bd970f64a6301fa75ae2eb22 26
通道里有个“12306预约检验通道”的标志来提醒

当你进入预约通道以后,会让你进行一个预检查登记,大概像下面这样,

d2b5ca33bd970f64a6301fa75ae2eb22 27

填写完成以后,从后备箱拿出三脚架,放在前挡风玻璃处,开车排队,并将车辆开上检验通道,你的事情就做完了,接下来只需要把钥匙交给工作人员,工作人员会将你的车开上检验通道,进行检查。

d2b5ca33bd970f64a6301fa75ae2eb22 28

此刻你只需要到办事大厅,缴费、拍照、等结果即可。汽车检查的速度很快,基本上我去交完费,等了 5 分钟,就已经完成检验,领钥匙走人了。

一些 Tips

  1. 年检的时候记得带上你的三脚架,检查的时候会用到。当然,更好的方案是把三脚架就放在后备箱里,别拿出来,避免用的时候没有。
  2. 记得提前预约,真的要好很多。
  3. 记得检查你的交强险状态,如果交强险时间较短,有可能会无法入库(但到底多短算短我也不知道),最好是保持2个月以上吧。

总结

汽车年检比我想象的要简单,之前我听别人讲,汽车年检都要耗费一天,甚至是需要找专人代为检查。一方面可能是因为他们的车辆较老,各项基础设施都已经出现故障,有可能因为故障的原因导致无法完成年检。另一方面,也可能是因为习惯了排队,而不是预约,导致车检的耗时比较长,所以也就不愿意自己去年检了。

Read More

Feed 订阅调整说明

本站一直以来,都开放了 Feed 的全文订阅,方便读者阅读,不过由于博客时间较长,文章越来越多,导致 Feed 越来越大,目前已经到达了 1.3 MB

d2b5ca33bd970f64a6301fa75ae2eb22 1

较大的 Feed 文件会导致在进行新读者加载时耗费较多时间,因此,我将 Feed 输出的条目调整为 50 条,内容依旧保持全文开启,以方便使用 Feed 订阅的同学。

d2b5ca33bd970f64a6301fa75ae2eb22 2

调整后,Feed 大小只有 64 KB,加载速度也更快。

如果你希望看到我所有的历史文章,可以直接访问本站的归档页面查看。