diff --git a/content/blog/cicd-github-actions.md b/content/blog/cicd-github-actions.md deleted file mode 100644 index 36e3763..0000000 --- a/content/blog/cicd-github-actions.md +++ /dev/null @@ -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. diff --git a/content/blog/go-concurrency.md b/content/blog/go-concurrency.md deleted file mode 100644 index 05efe52..0000000 --- a/content/blog/go-concurrency.md +++ /dev/null @@ -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. diff --git a/content/blog/media-test.md b/content/blog/media-test.md deleted file mode 100644 index 464cba0..0000000 --- a/content/blog/media-test.md +++ /dev/null @@ -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` 配置允许直接插入 ` - - -几个关键参数:`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 搭建个人站点,多媒体内容的处理原则就一条:**路径统一、格式克制、体积有数**。祝搭建愉快。 diff --git a/content/blog/mermaid-test.md b/content/blog/mermaid-test.md deleted file mode 100644 index 88354ad..0000000 --- a/content/blog/mermaid-test.md +++ /dev/null @@ -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
long time, so long
that the text does
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
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 -``` - - diff --git a/content/blog/prolog-pokemon.md b/content/blog/prolog-pokemon.md deleted file mode 100644 index 7dd6786..0000000 --- a/content/blog/prolog-pokemon.md +++ /dev/null @@ -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),共有超过一千种不同的宝可梦物种。 - -
皮卡丘始祖大鸟裹蜜虫
- -{{< 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 对你熟悉的领域建模——它会彻底改变你对数据和关系的思考方式 - -祝编码愉快,愿你的属性相克永远是效果绝佳。 diff --git a/content/blog/python-async-guide.md b/content/blog/python-async-guide.md deleted file mode 100644 index 4a1170a..0000000 --- a/content/blog/python-async-guide.md +++ /dev/null @@ -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. diff --git a/content/blog/site-launch-guide.md b/content/blog/site-launch-guide.md deleted file mode 100644 index d034ea5..0000000 --- a/content/blog/site-launch-guide.md +++ /dev/null @@ -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 }} -
- - {{ . }} - -
-{{- 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** | |