Remove 7 AI-generated test blog posts

This commit is contained in:
2026-06-17 22:00:17 +08:00
parent 9f547b0103
commit d1806c4def
7 changed files with 0 additions and 1159 deletions
-101
View File
@@ -1,101 +0,0 @@
---
title: "CI/CD Patterns with GitHub Actions"
date: 2026-05-15T09:00:00+08:00
draft: false
poet: |
大音希声,大象无形。
——老子,中国,约前571–约前471
description: "Build matrices, caching strategies, environment-specific deployments, and reusable workflows. Patterns that scale from solo projects to team repos."
tags:
- CI/CD
- GitHub
- DevOps
---
GitHub Actions is now the default CI for most open-source projects. Build matrices save config lines, caching `node_modules` and Go modules saves minutes per run. Reusable workflows keep things DRY across repos.
## Build Matrices
Don't copy-paste job definitions for different Node versions or OS targets. Use a matrix:
```yaml
jobs:
test:
strategy:
matrix:
node: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
```
This single definition expands to 6 parallel jobs. Add `fail-fast: false` if you want all runs to complete even when one fails — helpful for catching platform-specific bugs in one CI run.
## Smart Caching
Caching is the easiest way to cut CI minutes:
```yaml
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: npm-${{ runner.os }}-
```
The `restore-keys` fallback is important: even a partial cache hit saves time. For Go modules, cache `~/go/pkg/mod`. For Docker builds, use BuildKit's registry cache (`--cache-from type=registry`) for layer caching across CI runs.
## Environment-Specific Deployments
Use environments to gate production deploys:
```yaml
deploy-prod:
needs: [test, build]
environment: production
steps:
- run: ./deploy.sh production
```
The `environment: production` lets you set required reviewers, wait timers, and environment-specific secrets in the GitHub UI. No more accidentally deploying to prod from a feature branch.
## Reusable Workflows
When you maintain multiple repos, reusable workflows prevent drift:
```yaml
# .github/workflows/deploy.yml in a shared repo
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
AWS_ROLE:
required: true
```
Call it from any repo:
```yaml
jobs:
deploy:
uses: org/shared-workflows/.github/workflows/deploy.yml@main
with:
environment: staging
secrets:
AWS_ROLE: ${{ secrets.AWS_ROLE }}
```
Update the shared workflow once, and every repo benefits. This pattern scales well for orgs with 10+ services.
## Practical Tips
- Set `timeout-minutes` on every job — the default is 360 minutes, and you don't want a hung test burning your quota.
- Use `concurrency` to cancel redundant runs when pushing to the same PR multiple times.
- Pin action versions to SHA hashes for supply-chain security, not just tags.
-80
View File
@@ -1,80 +0,0 @@
---
title: "Go Concurrency Patterns in Practice"
date: 2026-05-15T10:00:00+08:00
draft: false
poet: |
To see a World in a Grain of Sand
And a Heaven in a Wild Flower
— William Blake, UK, 1757–1827
description: "Go concurrency beyond the basics. Worker pools for bounded parallelism, context propagation for cancellation, select loops for channel coordination — three patterns that hold up in production."
tags:
- Go
- Concurrency
- Programming
---
I've been writing Go for a few years now, and concurrency remains one of those topics where textbook knowledge doesn't quite prepare you for production. Here are a few patterns I've found genuinely useful.
## The Worker Pool
The classic. Useful when you need bounded parallelism:
```go
func workerPool(jobs <-chan Job, results chan<- Result, count int) {
var wg sync.WaitGroup
for i := 0; i < count; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
```
## Context Propagation
Probably the single most important practice I've adopted: **every long-running goroutine should accept a context**. It makes cancellation and timeout handling explicit rather than an afterthought.
```go
func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
// ...
}
```
## The Select Loop
When you need to coordinate multiple channels, `select` is your friend. A common pattern:
```go
for {
select {
case msg := <-messages:
handle(msg)
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
flush()
}
}
```
## Things I Wish I Knew Earlier
1. **Channel ownership** — the goroutine that writes should also close the channel
2. **Buffered channels aren't a fix for deadlocks** — they just push the problem further out
3. **`sync.WaitGroup` copies by value** — pass it by pointer, always
4. **The race detector is free** — `go test -race` should be part of your CI
Concurrency in Go is *easy to write* but *hard to get right*. The compiler won't save you from logical races. That's on us.
-63
View File
@@ -1,63 +0,0 @@
---
title: "Hugo 站点中嵌入视频与图片的最佳实践"
date: 2026-05-16T12:00:00+08:00
draft: false
poet: |
一花一世界,
一叶一菩提。
——《华严经》
description: "静态站点不等于只能放文字。B 站视频的自适应嵌入方案、图片懒加载与响应式处理、以及如何在 Hugo 里保持多媒体内容的可维护性。"
---
静态博客常被认为只能承载纯文本,但实际上 Hugo 对多媒体内容的支持相当完整。本文记录本站的视频嵌入和图片排版方案,供有类似需求的读者参考。
## B 站视频嵌入
Hugo 的 `unsafe: true` 配置允许直接插入 `<iframe>`,这让嵌入第三方播放器变得简单。下面是本站文章中使用的 B 站视频嵌入代码:
<div style="position: relative; padding-bottom: 75%; height: 0; overflow: hidden; margin: 1.5rem 0;">
<iframe
src="//player.bilibili.com/player.html?bvid=BV1AWRVB2EAz&page=1&high_quality=1&autoplay=0"
scrolling="no"
border="0"
frameborder="no"
framespacing="0"
allowfullscreen="true"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
</iframe>
</div>
几个关键参数:`high_quality=1` 默认启用高清画质,`autoplay=0` 禁用自动播放(避免对读者造成打扰)。外层容器的 `padding-bottom: 75%` 是 4:3 比例,如果视频是 16:9 改成 `56.25%` 即可。
## 图片排版实践
### 宽幅配图
对于需要烘托氛围的宽幅图片,直接使用 Markdown 默认语法即可,Hugo 会自动处理响应式缩放:
![AI 主题图](/images/ai.jpg)
{{< caption >}}宽幅配图适合放在文章开头,作为视觉呼吸点。{{< /caption >}}
这类图片适合放在文章开头或章节之间作为视觉呼吸点。
### 多图并列
需要对比展示时,可以将图片连续排列。配合 Hugo 的渲染引擎,相邻图片会自动获得合适的间距:
![编程](/images/coding.jpg)
{{< caption >}}编程与思考。{{< /caption >}}
![思考](/images/thinking.jpg)
### 配图居中
适量使用图片比堆砌更能提升阅读体验。一张位置恰当的配图可以给段落之间留出自然的停顿:
![子弹日记](/images/journal.jpg)
{{< caption >}}一张位置恰当的配图,给段落之间留出自然的停顿。{{< /caption >}}
## 多媒体文件的组织
本站将所有图片和字体统一放入 `static/` 目录,引用时使用绝对路径 `/images/xxx.jpg`。这样做的好处是路径清晰、不受页面层级影响,也方便后续迁移到 CDN。
如果你也在用 Hugo 搭建个人站点,多媒体内容的处理原则就一条:**路径统一、格式克制、体积有数**。祝搭建愉快。
-150
View File
@@ -1,150 +0,0 @@
---
title: "Mermaid 图表语法示例"
date: 2026-05-19T10:00:00+08:00
draft: false
description: "Mermaid 官方语法示例合集:流程图、时序图、类图、状态图、甘特图、饼图、Git 图等全部图表示范。"
tags:
- Mermaid
- 图表
- 语法
poet: |
大音希声,大象无形。
——老子,中国,约前571–约前471
---
> 原文:[Mermaid Examples](https://mermaid.js.org/syntax/examples.html)
# Examples
This page contains a collection of examples of diagrams and charts that can be created through mermaid and its myriad applications.
**If you wish to learn how to support mermaid on your webpage, read the [Beginner's Guide](../config/usage.md?id=usage).**
**If you wish to learn about mermaid's syntax, Read the [Diagram Syntax](../syntax/flowchart.md?id=flowcharts-basic-syntax) section.**
## Basic Pie Chart
```mermaid
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
```
```mermaid
pie title What Voldemort doesn't have?
"FRIENDS" : 2
"FAMILY" : 3
"NOSE" : 45
```
## Basic sequence diagram
```mermaid
sequenceDiagram
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
Alice->John: Yes... John, how are you?
```
## Basic flowchart
```mermaid
graph LR
A[Square Rect] -- Link text --> B((Circle))
A --> C(Round Rect)
B --> D{Rhombus}
C --> D
```
## SequenceDiagram: Loops, alt and opt
```mermaid
sequenceDiagram
loop Daily query
Alice->>Bob: Hello Bob, how are you?
alt is sick
Bob->>Alice: Not so good :(
else is well
Bob->>Alice: Feeling fresh like a daisy
end
opt Extra response
Bob->>Alice: Thanks for asking
end
end
```
## SequenceDiagram: Message to self in loop
```mermaid
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop HealthCheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts<br/>prevail...
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
## Sequence Diagram: Blogging app service communication
```mermaid
sequenceDiagram
participant web as Web Browser
participant blog as Blog Service
participant account as Account Service
participant mail as Mail Service
participant db as Storage
Note over web,db: The user must be logged in to submit blog posts
web->>+account: Logs in using credentials
account->>db: Query stored accounts
db->>account: Respond with query result
alt Credentials not found
account->>web: Invalid credentials
else Credentials found
account->>-web: Successfully logged in
Note over web,db: When the user is authenticated, they can now submit new posts
web->>+blog: Submit new post
blog->>db: Store post data
par Notifications
blog--)mail: Send mail to blog subscribers
blog--)db: Store in-site notifications
and Response
blog-->>-web: Successfully posted
end
end
```
## A commit flow diagram.
```mermaid
gitGraph:
commit "Ashish"
branch newbranch
checkout newbranch
commit id:"1111"
commit tag:"test"
checkout main
commit type: HIGHLIGHT
commit
merge newbranch
commit
branch b2
commit
```
<!--- cspell:ignore Ashish newbranch --->
-409
View File
@@ -1,409 +0,0 @@
---
title: "用宝可梦解释 Prolog 基础"
date: 2026-05-18T13:30:00+08:00
draft: false
description: "通过宝可梦对战系统理解逻辑编程语言 Prolog 的核心概念——事实、规则、查询,以及它为什么比 SQL 和电子表格更灵活。"
tags:
- Prolog
- 宝可梦
- 编程语言
- 翻译
poet: |
众里寻他千百度,
蓦然回首,那人却在,灯火阑珊处。
——辛弃疾,中国,1140–1207
---
> 原文作者:Alexander Petros
> 原文链接:[Prolog Basics Explained with Pokémon](https://unplannedobsolescence.com/blog/prolog-basics-pokemon/)
> 本文已获授权翻译,有删节和注释。
启发这篇文章的项目有点傻——我要详细描述一个儿童电子游戏的机制——但正是这个特定问题最终让我真正理解了 Prolog。这是自从读了 Bruce Tate 的《七周七语言》以来一直在追寻的顿悟。
对于某些类型的关系,逻辑编程是我用过的最简洁、最具表现力的编程系统。要理解为什么,让我们来聊聊宝可梦。
## 宝可梦基础
宝可梦是一个电子游戏系列,设定在一个人类与各种色彩缤纷的动物角色共存的世界。"Pokémon" 既是系列名称,也是这些动物角色的统称,每个角色都有自己的物种名称。从妙蛙种子(#1)到桃歹郎(#1025),共有超过一千种不同的宝可梦物种。
<div style="display:flex;gap:8px;justify-content:center;margin:1rem 0"><img src="/images/pokemon/pikachu.png" alt="皮卡丘" style="width:32%"><img src="/images/pokemon/archeops.png" alt="始祖大鸟" style="width:32%"><img src="/images/pokemon/dipplin.png" alt="裹蜜虫" style="width:32%"></div>
{{< caption >}}受欢迎的宝可梦包括(从左到右):皮卡丘(#25)、始祖大鸟(#567)和裹蜜虫(#1101)。{{< /caption >}}
现在有各种各样的宝可梦游戏,但主系列始终围绕着捕捉和对战。在对战中,你的六只宝可梦队伍与另一支队伍交锋。每只宝可梦配备四个招式,通常用来对对手造成伤害。你需要将对方所有宝可梦的 HP 减到零,同时防止对方先对你做同样的事。
每只宝可梦都有独特的特性影响对战表现:基础能力值、可学的招式、特性和属性组合。这里的庞大组合数量正是试图用软件来追踪这些信息的动机。
![巨钳螳螂的能力值(来自 Smogon)](/images/pokemon/scizor-stats.png)
{{< caption >}}巨钳螳螂是虫/钢属性,攻击力高但速度低(数据来自 Smogon)。{{< /caption >}}
**速度**决定哪个招式先出手;**攻击和特攻**分别影响物理招式和特殊招式的伤害;**防御和特防**影响受到的伤害。
**属性**尤其重要。招式有属性(如火或岩石),宝可梦可以拥有最多两种属性。如果招式属性对对方宝可梦效果绝佳,造成双倍伤害;效果不佳则只造成一半伤害。
![冲浪对月石效果绝佳](/images/pokemon/surf.png)
{{< caption >}}月石是岩石/超能力属性。岩石弱水,超能力对水中性,所以冲浪会造成 2 倍伤害。{{< /caption >}}
举个直观的例子:火属性的喷射火焰对草属性宝可梦造成 2 倍伤害(草弱火),但水属性的冲浪只造成 ½ 伤害(草抗水)。
属性修正可以叠加。巨钳螳螂是虫/钢属性,虫和钢都弱火,所以火属性招式对它造成 4 倍伤害。电弱水,但地面免疫电——如果你对水/地面的巨沼怪使用电属性招式,伤害为零,因为 0×2 还是 0。
![宝可梦属性相克表(来自 Wikimedia)](/images/pokemon/type-chart.svg)
{{< caption >}}宝可梦属性相克表。{{< /caption >}}
这些基本上就是我 8 岁时理解的宝可梦机制。点击招式造成伤害,尽量选择属性相克有利的招式。这些游戏是为儿童设计的,表面上看并不难。
## Prolog 基础
在解释宝可梦机制底层有多复杂之前,先解释逻辑编程的工作原理。宝可梦非常适合逻辑编程,因为宝可梦对战本质上是一个极其精密的规则引擎。
首先创建一个包含事实的文件:
```prolog
pokemon(bulbasaur).
pokemon(ivysaur).
pokemon(venusaur).
pokemon(charmander).
pokemon(charmeleon).
pokemon(charizard).
pokemon(squirtle).
pokemon(wartortle).
pokemon(blastoise).
```
在 Prolog 中,我们声明"谓词"(Predicate)。谓词定义关系:`bulbasaur` 是一个 `pokemon`,`charmander` 是一个 `pokemon`,以此类推。我们称这个谓词为 `pokemon/1`,因为它有一个参数。
这些事实被加载到一个交互式提示符——"顶层"(top-level)中。你输入一个语句到提示符,Prolog 试图找出所有使该语句为真的方式:
```
?- pokemon(squirtle).
true.
```
不是所有东西都是宝可梦。
```
?- pokemon(alex).
false.
```
接着添加宝可梦的属性,作为谓词 `type/2`:
```prolog
type(bulbasaur, grass).
type(bulbasaur, poison).
type(ivysaur, grass).
type(ivysaur, poison).
type(charmander, fire).
type(squirtle, water).
type(blastoise, water).
```
有些宝可梦只有一种属性,有些有两种。后一种情况用两个 `type` 事实建模。妙蛙种子是草属性,也是毒属性——两者都为真。
交互式查询:
```
?- type(squirtle, water).
true.
?- type(squirtle, grass).
false.
```
Prolog 中首字母大写的名字是**变量**。Prolog 尝试将谓词与变量的所有可能匹配进行"合一":
```
?- type(squirtle, Type).
Type = water.
```
对于有两种属性的宝可梦,谓词会合一两次:
```
?- type(venusaur, Type).
Type = grass
; Type = poison.
```
分号 `;` 表示"或"。任何参数都可以是变量,这意味着我们可以从任意方向提问。所有草属性宝可梦有哪些?只需把第一个参数设为变量:
```
?- type(Pokemon, grass).
Pokemon = bulbasaur
; Pokemon = ivysaur
; Pokemon = venusaur
; ...
```
逗号用于列出多个谓词——Prolog 会合一变量使得所有谓词都为真。列出所有水/冰属性的宝可梦:
```
?- type(Pokemon, water), type(Pokemon, ice).
Pokemon = dewgong
; Pokemon = cloyster
; Pokemon = lapras
; Pokemon = ironbundle
; false.
```
Iron Bundle 是一只水/冰属性宝可梦,特攻很高。具体多高?
```
?- pokemon_spa(ironbundle, SpA).
SpA = 124.
```
特攻这么高,我们想利用强力的特殊招式。Iron Bundle 会哪些特殊招式?
```
?- learns(ironbundle, Move), move_category(Move, special).
Move = blizzard
; Move = freezedry
; Move = hydropump
; Move = icebeam
; Move = icywind
; ...
```
Freeze-Dry 是一个特别好的特殊招式。找出所有特攻大于 120、会 Freeze-Dry 的冰属性宝可梦:
```
?- pokemon_spa(Pokemon, SpA), SpA #> 120, learns(Pokemon, freezedry), type(Pokemon, ice).
Pokemon = glaceon, SpA = 130
; Pokemon = kyurem, SpA = 130
; Pokemon = kyuremwhite, SpA = 170
; Pokemon = ironbundle, SpA = 124.
```
最后一个概念:**规则**(Rules)。规则有头部和主体,如果主体为真,则规则合一:
```prolog
damaging_move(Move) :-
move_category(Move, physical)
; move_category(Move, special).
```
```
?- damaging_move(tackle).
true.
?- damaging_move(rest).
false.
```
## SQL 对比
到目前为止展示的内容在逻辑上并不复杂——只是关于各种事实的"与"和"或"语句。不过请花一点时间体会一下,查询这个数据库比 SQL 舒服多少。
我会这样设置 SQL 表:
```sql
CREATE TABLE pokemon (pokemon_name TEXT, special_attack INTEGER);
CREATE TABLE pokemon_types(pokemon_name TEXT, type TEXT);
CREATE TABLE pokemon_moves(pokemon_name TEXT, move TEXT, category TEXT);
```
然后查询特攻 > 120、会 Freeze-Dry 的冰属性宝可梦:
```sql
SELECT DISTINCT pokemon, special_attack
FROM pokemon as p
WHERE p.special_attack > 120
AND EXISTS (SELECT 1 FROM pokemon_moves as pm
WHERE p.pokemon_name = pm.pokemon_name AND move = 'freezedry')
AND EXISTS (SELECT 1 FROM pokemon_types as pt
WHERE p.pokemon_name = pt.pokemon_name AND type = 'ice');
```
对比等效的 Prolog 查询:
```
?- pokemon_spa(Pokemon, SpA), SpA #> 120, learns(Pokemon, freezedry), type(Pokemon, ice).
```
我不是在贬低 SQL——我很喜欢 SQL——但 Prolog 版本简单和灵活得令人惊叹。如果我们继续添加更多条件,SQL 查询会变得难以管理,而 Prolog 查询仍然易于阅读和编辑。
## 升级
宝可梦对战拥有数量惊人的机制,以复杂且概率性的方式相互影响。我尚未提及的部分机制:
- 某些招式有一定概率落空
- 某些招式会提升或降低能力值
- 宝可梦可以携带具有各种效果的物品
- 伤害计算不是恒定的,而是呈正态分布
- 宝可梦可以被冰冻、灼伤、麻痹、中毒或陷入睡眠
- 各种场地效果(天气、戏法空间等)会改变招式伤害和出手顺序
- 每只宝可梦都有一个特性(如漂浮免疫地面招式、降雨改变天气、强行增伤 1.3 倍)
- 玩家分配努力值提升选定能力值
如果你想为这个游戏构建软件,挑战在于在所有复杂性的建模过程中不疯掉。Prolog 在这方面出奇地擅长,主要有两个原因:
- 查询模型擅长描述临时组合
- 数据模型非常适合以一致的方式分层叠加规则
为了说明这一点,以下是我为宝可梦选秀联赛实现优先度招式的过程。
宝可梦选秀大致就是字面意思。宝可梦根据实力被赋予分值,每个玩家获得一定分数,轮流选直到用完。你的队伍最终会有大约 8-11 只宝可梦,每周你和联赛中的另一个人正面交锋。
我定义的队伍:
```prolog
alex(meowscarada).
alex(weezinggalar).
alex(swampertmega).
alex(latios).
alex(volcarona).
alex(tornadus).
alex(politoed).
alex(archaludon).
alex(beartic).
alex(dusclops).
```
我的宝可梦里有哪些会 Freeze-Dry?
```
?- alex(Pokemon), learns(Pokemon, freezedry).
false.
```
一个都没有。唉。
非常重要的一类招式为**优先度招式**。大多数招式的优先度为零:
```
?- move_priority(Move, P).
Move = absorb, P = 0
; Move = accelerock, P = 1
; Move = acid, P = 0
; ...
```
Accelerock 的优先度为 1。使用它的宝可梦会先于任何使用优先度 0 招式的宝可梦行动,即使后者速度更高。
我定义一个 `learns_priority/3` 谓词:
```prolog
learns_priority(Pokemon, Move, P) :-
learns(Pokemon, Move),
move_priority(Move, P),
P #> 0.
```
"我的队伍学会了哪些优先度招式"——返回了大量答案,但很多是双打专用招式。过滤掉双打招式和保护招式:
```prolog
learns_priority(Mon, Move, Priority) :-
learns(Mon, Move),
\+ doubles_move(Move),
\+ protection_move(Move),
Move \= bide,
move_priority(Move, Priority),
Priority #> 0.
doubles_move(helpinghand).
doubles_move(allyswitch).
doubles_move(ragepowder).
protection_move(detect).
protection_move(protect).
protection_move(endure).
protection_move(magiccoat).
```
搞定!结果精简为真正有用的优先度招式:
```
?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).
Pokemon = meowscarada, Move = quickattack, Priority = 1
; Pokemon = meowscarada, Move = suckerpunch, Priority = 1
; Pokemon = beartic, Move = aquajet, Priority = 1
; Pokemon = dusclops, Move = shadowsneak, Priority = 1
; Pokemon = dusclops, Move = suckerpunch, Priority = 1.
```
优先度招式的缺乏实际上是我的队伍的一大弱点。
同样地查询对手的优先度招式也非常有用:
```
?- morry(Pokemon), learns_priority(Pokemon, Move, Priority).
Pokemon = mawilemega, Move = suckerpunch, Priority = 1
; Pokemon = walkingwake, Move = aquajet, Priority = 1
; ...
```
此时,Morry 提了一个挑战:恶作剧之心特性的宝可梦,其变化招式额外获得 +1 优先度。能否在规则中体现?
我的队伍恰好有一只这样的宝可梦——Tornadus。使用 Prolog 的 if/then 结构 `->/2`,三分钟搞定:
```prolog
learns_priority(Mon, Move, Priority) :-
learns(Mon, Move),
\+ doubles_move(Move),
\+ protection_move(Move),
Move \= bide,
move_priority(Move, BasePriority),
(
pokemon_ability(Mon, prankster), move_category(Move, status) ->
Priority #= BasePriority + 1
; Priority #= BasePriority
),
Priority #> 0.
```
现在查询会包含 Tornadus 所有变化招式的提升后优先度。
## 与电子表格的比较
宝可梦社区已经有类似的工具,构建在最优秀的编程界面上:朴素的电子表格。
![Techno 的准备文档](/images/pokemon/matchup-sheet.png)
{{< caption >}}Techno 的准备文档,一份功能强大的 Google Sheets。{{< /caption >}}
我使用一份被称为"Techno's Prep Doc"的 Google Sheets,输入队伍后自动生成海量对阵信息。
我好奇它的优先度招式查找公式是怎样的。
![优先级招式后端列表](/images/pokemon/priority-backend-list.png)
{{< caption >}}电子表格后端的硬编码招式列表。{{< /caption >}}
结果是——相当复杂:
```
={IFERROR(ARRAYFORMULA(VLOOKUP(FILTER(INDIRECT(Matchup!$S$3&"!$AV$4:$AV"),
INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"),
{Backend!$L$2:$L,Backend!$F$2:$F},2,FALSE))),
IFERROR(FILTER(INDIRECT(Matchup!$S$3&"!$AW$4:$AW"),
INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"))}
```
Prolog 的范式显然更具可扩展性。电子表格后端是一个硬编码的显要招式列表,而我的数据库可以查找**任何**招式。这个查询——找出 Tornadus 学会的、对 Justin 队伍中任何成员效果绝佳的特殊招式——在现有工具中根本不存在。我只用了 30 秒就拼出来了:
```
?- justin(Target), learns(tornadus, Move),
super_effective_move(Move, Target), move_category(Move, special).
Target = charizardmegay, Move = chillingwater
; Target = scizor, Move = heatwave
; Target = scizor, Move = incinerate
; Target = screamtail, Move = sludgebomb
; ...
```
## 最后的思考
这个项目始于一个玩笑——"如果我拿 Prolog 来做宝可梦组队会怎样"——但它教给我的逻辑编程知识比任何教科书都多。
我最大的领悟是:**Prolog 迫使你思考什么是真的,而不是做什么。** 在命令式编程中,大部分精力花在控制流上。在 Prolog 中,你只需陈述什么是真的,让求解器处理剩下的事。
对于一个有着数千条互动规则的规则引擎(比如宝可梦对战),这简直是天启。与其写 if/else 链检查属性相克,不如声明属性表,让 Prolog 处理其余部分。
如果你感兴趣,推荐:
1. 安装 SWI-Prolog 并玩玩 [prologdex](https://github.com/alexpetros/prologdex) 数据集
2. 阅读 [Adventure in Prolog](http://www.amzi.com/AdventureInProlog/) 教程
3. 用 Prolog 对你熟悉的领域建模——它会彻底改变你对数据和关系的思考方式
祝编码愉快,愿你的属性相克永远是效果绝佳。
-70
View File
@@ -1,70 +0,0 @@
---
title: "Python Async: Beyond the Basics"
date: 2026-05-12T11:00:00+08:00
draft: false
poet: |
They also serve who only stand and wait.
— John Milton, UK, 1608–1674
description: "Understanding the event loop, writing proper async context managers, and avoiding the most common pitfalls with asyncio.gather and cancel scopes."
tags:
- Python
- Async
- Programming
---
Async Python is no longer optional if you're doing any kind of I/O-bound work. Beyond the basics of `async`/`await`, understanding the event loop helps debug elusive issues.
## The Event Loop in Practice
The event loop runs one coroutine at a time, switching at `await` points. This is cooperative multitasking — a coroutine that never awaits blocks everything else. If you have CPU-bound work inside an async function, offload it to a thread pool with `asyncio.to_thread()`.
```python
import asyncio
# Bad: blocks the event loop
async def bad():
result = heavy_computation() # CPU-bound, no await
return result
# Good: offloads to a thread
async def good():
result = await asyncio.to_thread(heavy_computation)
return result
```
## Async Context Managers
Writing proper async context managers is essential for managing connections and locks:
```python
class AsyncConnection:
async def __aenter__(self):
self.conn = await create_connection()
return self.conn
async def __aexit__(self, *args):
await self.conn.close()
```
The `contextlib.asynccontextmanager` decorator makes this even cleaner for simple cases.
## Common Pitfalls with `asyncio.gather`
`gather` runs tasks concurrently, but one failing task doesn't cancel the others by default. If you need all-or-nothing semantics, use `asyncio.TaskGroup` (Python 3.11+):
```python
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(fetch(url1))
t2 = tg.create_task(fetch(url2))
# Both succeeded, or both were cancelled
```
## Cancel Scopes
Cancellation in asyncio is cooperative: `asyncio.CancelledError` is raised at the next `await`. If your coroutine catches and suppresses it, the task becomes uncancellable — usually a bug. Use `asyncio.shield()` sparingly to protect critical cleanup code.
## Structured Concurrency
The trend across languages (Trio in Python, Kotlin coroutines, Swift concurrency) is toward structured concurrency: every task has a clear parent scope that determines its lifetime. `TaskGroup` is Python's step in this direction. Embrace it over bare `create_task()` calls.
Async Python has matured significantly. Most of the old footguns have been addressed, but the fundamentals — understand the event loop, respect cancellation, and structure your concurrency — remain essential.
-286
View File
@@ -1,286 +0,0 @@
---
title: "域名折腾记录"
date: 2026-05-21T15:00:00+08:00
draft: false
description: "基于腾讯云的域名申请、工信部 ICP 备案、SSL 证书部署、Nginx HTTPS 配置、公安备案的全流程实操记录。"
tags:
- 教程
- 建站
- Nginx
- HTTPS
poet: |
千门万户曈曈日,
总把新桃换旧符。
——王安石,中国,1021–1086
---
把个人网站从 `http://IP` 变成一个正经的 `https://域名`,中间要走的路比想象中多。这篇文章记录我的实际操作过程,供有同样需求的朋友参考。
## 前提
- 服务器:腾讯云 Lighthouse,Ubuntu 24.04,4 核 4G
- 网站:Hugo 静态博客,Nginx 提供服务
- 域名注册商:腾讯云
## 一、域名申请
**控制台路径**:腾讯云控制台 → 产品 → 域名注册 → 域名注册
在腾讯云域名注册页面搜索想要的域名,`.com` 早就被抢光了,`.net` 还有一些。我选了 `fukun.net`,首年 90 元。
购买后进入 **控制台 → 产品 → 域名注册 → 我的域名**,找到刚买的域名,点击「实名认证」。上传身份证正反面,审核大约半小时到两小时。通过后域名状态变为「正常」。
回到我的域名页面,点击域名进入 **域名管理 → DNS 解析 → 添加记录**:
| 主机记录 | 记录类型 | 记录值 |
|---------|---------|--------|
| @ | A | 62.234.90.54 |
| www | A | 62.234.90.54 |
TTL 默认 600(即 10 分钟)即可。关于 TTL 的设置经验:
- **日常使用**:600 足够。DNS 修改后最迟 10 分钟全球生效,实际通常更快。
- **迁移前夕**:如果计划更换服务器 IP,提前 24 小时把 TTL 降到 60–120,这样迁移时 DNS 切换更快。
- **不要设太短**:TTL 过短(如 10 秒)会增加 DNS 查询量,对个人站点没必要。
- **不要设太长**:TTL 过长(如 86400)会导致修改后一天才能生效,出问题时无法快速切换。
生效很快,几分钟后 `ping fukun.net` 就能看到正确 IP。
## 二、工信部 ICP 备案
这是整个流程里最耗时的环节。根据《互联网信息服务管理办法》,中国大陆境内提供互联网信息服务的网站必须取得 ICP 备案号。
**控制台路径**:腾讯云控制台 → 产品 → 网站备案 → 开始备案
### 操作流程
1. 进入备案系统,选择「首次备案」
2. 填写主体信息(个人姓名、身份证号、详细通信地址)
3. 填写网站信息:
- 网站名称:注意不能包含「中国」「中华」等字样,个人备案也不能用太过商业化的名称(我填了「FuKun 技术笔记」)
- 网站域名:`fukun.net`
- 网站简介:如实描述,不要写「论坛」「商城」等需要前置审批的内容
4. 上传证件照片(身份证正反面、手持身份证照)
5. 进行人脸识别验证(微信扫码完成)
6. 提交初审
### 时间线
| 阶段 | 耗时 | 说明 |
|------|------|------|
| 腾讯云初审 | 1 个工作日 | 审核材料完整性,不合格会退回修改 |
| 提交管局 | 3 个工作日 | 初审通过后提交至通信管理局 |
| 工信部短信核验 | 即时 | 收到短信后 24 小时内点击链接确认 |
| 通信管理局审核 | 10–15 个工作日 | 各地管局速度不同,广东大约 12 天 |
我从提交到拿到备案号大约用了 **12 个工作日**。通过后会在 **控制台 → 网站备案 → 我的备案** 看到备案号和电子证书。
### 关键注意事项
- 备案期间网站**必须关闭**(Nginx 先别配域名,或者返回 403)
- 个人备案不得涉及企业、商品、新闻等内容
- 备案号需要在网站底部展示,链接到 `beian.miit.gov.cn`
- 备案通过后 30 天内需完成公安备案
## 三、SSL 证书申请
备案通过后域名可以解析了,此时网站还是 HTTP。下一步申请 SSL 证书开启 HTTPS。
**控制台路径**:腾讯云控制台 → 产品 → SSL 证书 → 我的证书 → 申请免费证书
我使用的是腾讯云「SSL 单域名证书(一年期)」,TrustAsia DV 品牌,RSA 2048 位加密,SHA256 签名算法。证书覆盖主域名 `fukun.net` 和 `www.fukun.net`。
证书链:`fukun.net` → TrustAsia DV TLS RSA CA 2025 → DigiCert Global Root G2。
### 操作步骤
1. 在 SSL 证书控制台点击「申请免费证书」(或购买对应类型的证书,¥64.6/年)
2. 填写申请信息:
- 证书绑定域名:`fukun.net`
- 选择「自动添加 DNS 验证记录」— 腾讯云会自动添加一条 TXT 记录
3. 提交申请,等待验证通过(通常 1–5 分钟)
4. 验证通过后状态变为「已签发」,点击「下载」
5. 选择「Nginx」格式下载,得到一个 zip 包
解压后得到四个文件:
| 文件 | 用途 |
|------|------|
| `fukun.net.csr` | 证书请求文件,申请时自动生成,部署不需要 |
| `fukun.net.key` | 私钥,Nginx ssl_certificate_key 使用 |
| `fukun.net_bundle.crt` | CRT 格式证书,含中间证书,Nginx 使用 |
| `fukun.net_bundle.pem` | PEM 格式证书,与 CRT 同内容,备用 |
## 四、Nginx SSL 配置
### 上传证书到服务器
```bash
# 将证书文件传到服务器
scp fukun.net.key fukun.net_bundle.crt ubuntu@62.234.90.54:~/
# SSH 登录服务器
ssh ubuntu@62.234.90.54
# 移动到 Nginx SSL 目录并设置权限
sudo mv ~/fukun.net.key ~/fukun.net_bundle.crt /etc/nginx/ssl/
sudo chown root:root /etc/nginx/ssl/fukun.net*
sudo chmod 600 /etc/nginx/ssl/fukun.net.key
```
### Nginx 站点配置
编辑 `/etc/nginx/sites-available/hugo-blog`:
```nginx
# HTTP → HTTPS 重定向
server {
listen 80;
server_name fukun.net www.fukun.net;
return 301 https://$host$request_uri;
}
# HTTPS 站点
server {
listen 443 ssl http2;
server_name fukun.net www.fukun.net;
ssl_certificate /etc/nginx/ssl/fukun.net_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/fukun.net.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
root /home/hugo/hugo-blog/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# 静态资源缓存
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
```
```bash
# 启用站点
sudo ln -sf /etc/nginx/sites-available/hugo-blog /etc/nginx/sites-enabled/
# 测试配置
sudo nginx -t
# 重载 Nginx
sudo systemctl reload nginx
```
## 五、备案号悬挂
工信部要求备案号须挂在网站首页底部。在 Hugo 的 `hugo.yaml` 配置:
```yaml
params:
footer:
icp: "粤ICP备2026061516号"
```
在布局文件 `layouts/_partials/footer.html` 中渲染:
```go-html-template
{{- with .Site.Params.footer.icp }}
<div class="hx:mt-2 hx:text-xs">
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">
{{ . }}
</a>
</div>
{{- end }}
```
## 六、公安部网站备案
ICP 备案通过后 30 天内,还需进行公安机关互联网站备案。注意需要先完成主体备案,再完成网站备案,两步都要做。
**网站**:www.beian.gov.cn → 全国公安机关互联网站安全服务平台
**第一步:主体备案**
1. 注册账号并登录
2. 进入「办事大厅」→「开办主体管理」→「新增主体」
3. 填写主体信息(个人姓名、身份证号、地址、联系方式等)
4. 提交审核,通过后可进行网站备案
**第二步:网站备案**
1. 进入「办事大厅」→「新办网站申请」
2. 填写网站信息:网站名称、域名、IP 地址、服务器所在地
3. 选择网络接入服务商:腾讯云
4. 提交审核(约 7–10 个工作日)
5. 审核通过后获取公安备案号,格式为「粤公网安备 XXXXXXX 号」
公安备案号也需要悬挂在网站上(通常放在 ICP 备案号旁边)。
## 七、新增二级域名
网站上线后如需新增二级域名(如 `gallery.fukun.net`),二级域名与主域名共用同一个 ICP 备案号,公安备案同理,不需要重复办理。只需要做好DNS解析、证书申请、Nginx配置。
**DNS 解析**:在域名管理 → DNS 解析中新增 A 记录,主机记录填 `gallery`,指向同一 IP。
**SSL 证书**:单域名证书覆盖不到新二级域名,需要为新二级域名单独申请一张(流程同第三章,约 5 分钟)。
**Nginx 配置**:新增站点文件 `/etc/nginx/sites-available/gallery`:
```nginx
server {
listen 443 ssl http2;
server_name gallery.fukun.net;
ssl_certificate /etc/nginx/ssl/gallery_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/gallery.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
root /home/hugo/blog-site/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
# HTTP 跳转 HTTPS
server {
listen 80;
server_name gallery.fukun.net;
return 301 https://$host$request_uri;
}
```
启用并重载:
```bash
sudo ln -sf /etc/nginx/sites-available/gallery /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
| 事项 | 耗时 | 费用 | 控制台路径 |
|------|------|------|-----------|
| 云服务器 | 1 天 | ¥99/月 | 控制台 → Lighthouse |
| 域名注册 | — | ¥90/年 | 控制台 → 域名注册 |
| 实名认证 | — | — | 控制台 → 域名注册 → 我的域名 |
| DNS 解析 | — | — | 域名管理 → DNS 解析 |
| ICP 备案 | 10–15 工作日 | — | 控制台 → 网站备案 |
| SSL 证书 | — | ¥64.6/年 | 控制台 → SSL 证书 → 我的证书 |
| CDN 加速 | — | ¥14/年 | 控制台 → CDN |
| 云硬盘扩容 | — | ¥70 | 控制台 → Lighthouse → 云硬盘 |
| Nginx 配置 | — | — | 无 |
| 公安备案 | 7–10 工作日 | — | beian.gov.cn |
| **合计** | **约 25–33 天** | **¥337.6** | |