<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Python on Jeanphilo Blog</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/</link><description>Recent content in Python on Jeanphilo Blog</description><generator>Hugo -- 0.159.2</generator><language>zh-cn</language><lastBuildDate>Thu, 29 Jan 2026 15:16:00 +0800</lastBuildDate><atom:link href="https://shio-chan-dev.github.io/jeanblog/zh/dev/python/index.xml" rel="self" type="application/rss+xml"/><item><title>请求日志一定要带 RequestId 吗？Python 成熟实践与落地指南</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/request-id-logging-best-practices/</link><pubDate>Thu, 29 Jan 2026 15:16:00 +0800</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/request-id-logging-best-practices/</guid><description>系统回答“日志是否需要 requestId”，并给出 Python 工业级自动注入方案、工程场景与排查流程。</description></item><item><title>任务编排为什么要放后端：让流程可控、可变、可回放</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pipeline-orchestration/</link><pubDate>Tue, 13 Jan 2026 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pipeline-orchestration/</guid><description>用通俗语言解释“后端任务编排”的必要性：把流程顺序从前端移到后端，用配置驱动步骤与等待点。给出最小可运行示例、落地步骤与常见坑。</description></item><item><title>Python 抽象基类 ABC vs ABCMeta：什么时候用哪个？</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/abc-vs-abcmeta-hugo/</link><pubDate>Fri, 12 Dec 2025 23:52:00 +0800</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/abc-vs-abcmeta-hugo/</guid><description>用可运行示例讲清 ABC 与 ABCMeta 的区别：一个是便捷基类，一个是元类；分别适合接口约束与类创建期的自动注入/校验。</description></item><item><title>从参数直传到Pipeline: 一次可复现可观测的数据处理管线改造实践</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/from-direct-params-to-config-driven-etl-pipeline/</link><pubDate>Wed, 10 Dec 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/from-direct-params-to-config-driven-etl-pipeline/</guid><description>&lt;h1 id="从参数直传到-pipeline一次可复现可观测的数据处理管线改造实践"&gt;从参数直传到 Pipeline：一次可复现、可观测的数据处理管线改造实践&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;副标题：&lt;/strong&gt; 为什么当处理链变得越来越长时，“配置驱动 + 上下文 + 外部存储”的 Pipeline 模式会比“参数直传”更适合工作？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;标签：&lt;/strong&gt; Python / Pipeline / ETL / 数据工程 / 架构设计
&lt;strong&gt;适读人群：&lt;/strong&gt; 后端开发、数据工程师、做文档处理/Embedding/索引构建的同学
&lt;strong&gt;阅读时间：&lt;/strong&gt; 10–15 min
&lt;strong&gt;摘要：&lt;/strong&gt; 本文记录我从“领域模型传来传去”的后端式写法，迁移到“配置驱动 Pipeline”模式的过程，总结落地要点、踩过的坑，以及为什么这种模式更适合复杂的数据加工链。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="-写这篇文章的动机"&gt;🧭 写这篇文章的动机&lt;/h1&gt;
&lt;p&gt;做后端时，我长期习惯一种简单粗暴的风格：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;需要什么参数就一直往下传，函数链一路 call 下去。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很多业务都是这么写的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入是个模型/DTO&lt;/li&gt;
&lt;li&gt;处理完传下一个函数&lt;/li&gt;
&lt;li&gt;大对象在链路里飘来飘去&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但当我开始做 &lt;strong&gt;文档处理、Embedding、实体提取、索引构建&lt;/strong&gt; 这种“多步骤、可重跑、需观测”的任务时，这种写法很快失效：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要重跑某一步时必须重建整条调用链&lt;/li&gt;
&lt;li&gt;中间产物无法落地检查&lt;/li&gt;
&lt;li&gt;改一个策略需要改一堆函数签名&lt;/li&gt;
&lt;li&gt;并发 / 异常恢复都难处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后来接触到构建数据处理 Workflow / ETL Pipeline 的方式，发现它的核心思路完全不一样：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;配置驱动策略 → 上下文承载运行态 → 外部存储承载数据流。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这套体系让多步处理链突然变得可插拔、能恢复、能观测、能重放。
于是就有了这篇文章，把我的心智迁移过程与实践要点记录下来。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="-tldr你只看这一屏也能理解本文核心"&gt;⚡ TL;DR（你只看这一屏也能理解本文核心）&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;配置驱动：&lt;/strong&gt; 路径、模型、超参都写 config，而不是塞进函数参数里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文 context：&lt;/strong&gt; 统一管理 I/O 句柄、缓存、回调、统计、运行时标志。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部存储：&lt;/strong&gt; 步骤间不传大对象，读写约定表名：&lt;code&gt;documents → text_units → entities → index&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可插拔 Pipeline：&lt;/strong&gt; “步骤名 → 函数指针”的顺序列表，可一键切换 Standard/Fast 等方案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等与恢复：&lt;/strong&gt; 中间表持久化，可覆盖或版本化，崩溃后能断点续跑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;观测与回调：&lt;/strong&gt; start/end/进度统一上报，产出 stats.json，定位问题更快。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步友好：&lt;/strong&gt; 步骤 await 执行，内部可分片并发或调用 LLM。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取舍：&lt;/strong&gt; 成本是心智负担增加；收益是可观察、可重跑、可替换、低耦合。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id="-目标读者"&gt;👥 目标读者&lt;/h1&gt;
&lt;p&gt;适合以下同学阅读：&lt;/p&gt;</description></item><item><title>Pydantic vs dataclass vs TypedDict：谁负责什么，怎么组合？</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pydantic-vs-dataclass-vs-typeddict/</link><pubDate>Tue, 09 Dec 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pydantic-vs-dataclass-vs-typeddict/</guid><description>&lt;h3 id="标题"&gt;标题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Pydantic vs dataclass vs TypedDict：谁负责什么，怎么组合？&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="副标题--摘要"&gt;副标题 / 摘要&lt;/h3&gt;
&lt;p&gt;承接《别让 Pydantic 占领你的整个项目》，这一篇用对比视角把 Pydantic、dataclass、TypedDict 的定位、取舍和组合方式讲清楚：&lt;strong&gt;谁用于 API 校验、谁承载业务状态、谁只做类型提示&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="目标读者"&gt;目标读者&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;FastAPI / Pydantic 用户，想搞清楚“数据类”该放在哪一层&lt;/li&gt;
&lt;li&gt;有 0–5 年经验、在做服务端建模的 Python 工程师&lt;/li&gt;
&lt;li&gt;已读过前一篇分层文章，想进一步对比具体工具&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="背景--动机为什么要区分三者"&gt;背景 / 动机：为什么要区分三者？&lt;/h3&gt;
&lt;p&gt;在上一篇里，我们强调“Pydantic 应该停留在 API/外围”。很多同学随后会问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“那 Python 原生 dataclass 呢？和 Pydantic 有什么差？”&lt;/li&gt;
&lt;li&gt;“TypedDict 是不是又一个‘数据类’，要不要取代 dataclass？”&lt;/li&gt;
&lt;li&gt;“什么时候该用 Pydantic dataclasses，什么时候用标准库？”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不区分清楚，常见后果有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 TypedDict 写业务逻辑，测试时才发现它根本不做运行时校验；&lt;/li&gt;
&lt;li&gt;用 Pydantic BaseModel 传来传去，导致 Domain 强绑定外部依赖；&lt;/li&gt;
&lt;li&gt;dataclass 和 Pydantic 混用，序列化和校验边界越来越模糊。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="核心概念一句话定位"&gt;核心概念：一句话定位&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pydantic BaseModel&lt;/strong&gt;：运行时校验 + 类型转换 + JSON 友好；属于“对外/边界”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dataclass（标准库）&lt;/strong&gt;：轻量数据载体，可承载业务方法；不做自动校验，属于“领域/内部”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypedDict&lt;/strong&gt;：仅提供静态类型提示，运行时就是普通 dict；属于“静态约束/外部协议”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主要差异：&lt;/p&gt;</description></item><item><title>以业务对象为核心的 Python 架构实践</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/python-business-object-first-architecture/</link><pubDate>Mon, 01 Dec 2025 11:00:00 +0800</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/python-business-object-first-architecture/</guid><description>通过一个工单（Ticket）示例，讲解在 Python + FastAPI + SQLAlchemy 项目中，如何先定义清晰的业务对象（领域模型），再围绕它设计接口 DTO、仓储抽象和应用服务，避免业务代码直接围绕表结构和框架 API 打转。</description></item><item><title>从表结构到领域模型：用聚合仓储设计权限系统</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/permission-architecture-aggregate-repository/</link><pubDate>Mon, 01 Dec 2025 10:00:00 +0800</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/permission-architecture-aggregate-repository/</guid><description>本文通过 FastAPI + SQLAlchemy + Alembic 的权限组管理示例，讲解如何用领域模型和仓储抽象设计权限系统，为什么仓储可以一次操作多张表，以及 service 层和 repository 层各自应该承担的职责。</description></item><item><title>Alembic 入门：第一次用 SQLAlchemy 做数据库迁移</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/alembic-intro-sqlalchemy-migrations/</link><pubDate>Fri, 28 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/alembic-intro-sqlalchemy-migrations/</guid><description>&lt;h1 id="-alembic-入门第一次用-sqlalchemy-做数据库迁移"&gt;🐣 Alembic 入门：第一次用 SQLAlchemy 做数据库迁移&lt;/h1&gt;
&lt;h2 id="-副标题--摘要"&gt;💡 副标题 / 摘要&lt;/h2&gt;
&lt;p&gt;如果你已经在用 SQLAlchemy 操作数据库，却还在靠“手工改表结构 + 导出导入 SQL”来维护 schema，这篇文章会带你用最小成本上手 Alembic。&lt;br&gt;
我们会从 &lt;strong&gt;0 配置 Alembic&lt;/strong&gt; 开始，一步步完成：生成迁移、升级/回滚数据库、和 SQLAlchemy 模型联动。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-目标读者"&gt;🎯 目标读者&lt;/h2&gt;
&lt;p&gt;适合这样的你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已经在项目中使用 &lt;strong&gt;SQLAlchemy&lt;/strong&gt;（ORM 或 Core 都行）；&lt;/li&gt;
&lt;li&gt;从未使用过 Alembic，或只懂 &lt;code&gt;alembic upgrade head&lt;/code&gt; 这几个命令；&lt;/li&gt;
&lt;li&gt;想为自己的项目加上 &lt;strong&gt;可回滚、可追踪、可审计&lt;/strong&gt; 的数据库结构变更；&lt;/li&gt;
&lt;li&gt;以 Python / Web 后端为主（Flask / FastAPI / 自研框架均可）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="-背景--动机为什么需要数据库迁移工具"&gt;🔥 背景 / 动机：为什么需要数据库迁移工具？&lt;/h2&gt;
&lt;p&gt;没有 Alembic 时，我们通常怎么改数据库结构？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在本地手改表结构（改字段、加索引）；&lt;/li&gt;
&lt;li&gt;导出 SQL 发给同事 / DBA；&lt;/li&gt;
&lt;li&gt;生产环境再手工执行一次；&lt;/li&gt;
&lt;li&gt;一旦出错，回滚非常痛苦。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见痛点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多人协作困难&lt;/strong&gt;：谁先改？谁后改？改了什么？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境不一致&lt;/strong&gt;：本地、测试、生产的表结构经常不一样；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;难以回滚&lt;/strong&gt;：一旦上线发现问题，很难安全退回之前版本；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计困难&lt;/strong&gt;：几年后根本不知道这个表为什么多了几个字段。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alembic 做的事情可以总结为一句话：&lt;/p&gt;</description></item><item><title>如何干预 Alembic：从自动生成到精细控制</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/alembic-autogenerate-to-manual-control/</link><pubDate>Fri, 28 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/alembic-autogenerate-to-manual-control/</guid><description>&lt;h1 id="-如何干预-alembic从自动生成到精细控制"&gt;🧬 如何干预 Alembic：从自动生成到精细控制&lt;/h1&gt;
&lt;h2 id="-副标题--摘要"&gt;💡 副标题 / 摘要&lt;/h2&gt;
&lt;p&gt;大多数人用 Alembic 的方式是：改 SQLAlchemy 模型 → &lt;code&gt;alembic revision --autogenerate&lt;/code&gt; → &lt;code&gt;alembic upgrade head&lt;/code&gt;。&lt;br&gt;
但在真实项目里，你往往需要“插手”这条流水线：控制生成的迁移内容、插入数据迁移、在生产环境加保护、按分支管理多套 Schema……&lt;/p&gt;
&lt;p&gt;这篇文章会带你系统认识 &lt;strong&gt;“如何干预 Alembic”&lt;/strong&gt;：&lt;br&gt;
从 &lt;code&gt;env.py&lt;/code&gt; 到单个迁移脚本，从自动生成到手写数据迁移，让你能放心地在生产库上使用 Alembic，而不是被它“牵着走”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-目标读者"&gt;🎯 目标读者&lt;/h2&gt;
&lt;p&gt;适合以下读者：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已在项目中使用 &lt;strong&gt;SQLAlchemy + Alembic&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;希望从“只会用 autogenerate”进阶到“懂得控制 Alembic 行为”；&lt;/li&gt;
&lt;li&gt;有生产库 / 多环境（dev、staging、prod）场景，需要更安全的迁移控制；&lt;/li&gt;
&lt;li&gt;想把 &lt;strong&gt;数据迁移&lt;/strong&gt;、&lt;strong&gt;自定义检查&lt;/strong&gt;、&lt;strong&gt;安全保护&lt;/strong&gt; 加进 Alembic 流程的后端工程师。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="-背景--动机为什么要干预-alembic"&gt;🔥 背景 / 动机：为什么要“干预” Alembic？&lt;/h2&gt;
&lt;p&gt;只使用 Alembic 的默认玩法，很容易遇到这些问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--autogenerate&lt;/code&gt; 生成了一堆你不理解的操作，不敢在生产上跑；&lt;/li&gt;
&lt;li&gt;模型删了字段，自动生成的迁移脚本也直接删列，但生产上其实还有老数据需要兜底；&lt;/li&gt;
&lt;li&gt;想在迁移时顺便初始化一些字典表、配置表，但不知放在哪；&lt;/li&gt;
&lt;li&gt;有些表只在测试 / demo 环境需要，生产环境不想创建；&lt;/li&gt;
&lt;li&gt;多个服务共享一个数据库，需要 &lt;strong&gt;按分支/模块控制迁移范围&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要解决这些问题，你就必须学会：&lt;br&gt;
&lt;strong&gt;在 Alembic 的各个“接缝处”插入自己的逻辑&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>让 FastAPI 异步真正‘不卡’：asyncio.create_task + to_thread 并发实践（含 MySQL 写入）</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/fastapi-asyncio-create-task-to-thread-mysql/</link><pubDate>Wed, 19 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/fastapi-asyncio-create-task-to-thread-mysql/</guid><description>把同步重活丢给线程、把可并行的子流程拆出来并发执行，让 FastAPI WebSocket/HTTP 服务在高并发文件处理场景下保持流畅与可靠。</description></item><item><title>现代加密替代方案：AES‑GCM 与 ChaCha20‑Poly1305 实战指南（附 Python 示例）</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/modern-crypto-aes-gcm-chacha20-poly1305-guide/</link><pubDate>Wed, 19 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/modern-crypto-aes-gcm-chacha20-poly1305-guide/</guid><description>聚焦现代 AEAD：为何替代 RC4、如何安全落地 AES‑GCM 与 ChaCha20‑Poly1305，附可复制的 Python 代码与最佳实践。</description></item><item><title>用 Python 还原 RC4 + JWT + 自定义 SSO Token 加解密（含可运行示例）</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/recreate-rc4-jwt-custom-sso-token-in-python/</link><pubDate>Wed, 19 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/recreate-rc4-jwt-custom-sso-token-in-python/</guid><description>从核心概念到可运行代码，演示用 Python 实现 RC4 加/解密、JWT 与自定义 SSO Token，并讨论风险与替代方案。示例中密钥与发行方均为占位值。</description></item><item><title>用一段优雅的python代码，把sqlalchemy模型高效转为字典</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/sqlalchemy-model-to-dict-python/</link><pubDate>Wed, 12 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/sqlalchemy-model-to-dict-python/</guid><description>&lt;p&gt;&lt;strong&gt;标题&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用一段优雅的 Python 代码，把 SQLAlchemy 模型安全、高效地序列化成字典&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;副标题 / 摘要&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SQLAlchemy 模型转字典（dict）看似简单，却暗藏字段格式、关系递归、循环引用等坑。本文通过一段实战代码，带你实现一个可复用的 &lt;code&gt;_to_dict&lt;/code&gt; 序列化工具，并分析其设计取舍与改进方向，适合正在用 SQLAlchemy 写后端接口的你。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;目标读者&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这篇文章适合以下读者：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;SQLAlchemy&lt;/strong&gt; 做 ORM 的后端开发者&lt;/li&gt;
&lt;li&gt;想把 &lt;strong&gt;ORM 模型转换为 JSON/dict&lt;/strong&gt; 的 Python 工程师&lt;/li&gt;
&lt;li&gt;对 &lt;strong&gt;模型序列化规范化&lt;/strong&gt; 有需求的中级开发者&lt;/li&gt;
&lt;li&gt;使用 Flask/FastAPI/Django + SQLAlchemy 的同学&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一背景--动机为什么要自己写-_to_dict"&gt;一、背景 / 动机：为什么要自己写 &lt;code&gt;_to_dict&lt;/code&gt;？&lt;/h2&gt;
&lt;p&gt;在 Web 开发中，我们几乎每天都要做一件事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;把数据库里的 ORM 对象，转成可以 JSON 响应给前端的数据结构（通常是 dict / list）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;乍一看好像只是 &lt;code&gt;obj.__dict__&lt;/code&gt; 或用个 &lt;code&gt;asdict&lt;/code&gt; 就完事，但现实中的问题包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;日期时间字段无法直接 JSON 化&lt;/strong&gt;：
&lt;code&gt;datetime&lt;/code&gt; / &lt;code&gt;date&lt;/code&gt; 对象不能直接 JSON 序列化，必须格式化成字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关系字段怎么处理？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一对多 / 多对多（&lt;code&gt;uselist=True&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;一对一 / 多对一（&lt;code&gt;uselist=False&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;避免递归爆炸&lt;/strong&gt;：
两个模型互相关联，很容易序列化时陷入无限递归。&lt;/p&gt;</description></item><item><title>别让Pydantic占领你的整个项目:聊聊API校验,Domain模型和数据库之间的边界</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pydantic-boundaries-api-domain-db/</link><pubDate>Sun, 09 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pydantic-boundaries-api-domain-db/</guid><description>&lt;h3 id="标题"&gt;标题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;别让 Pydantic 占领你的整个项目：聊聊 API 校验、Domain 模型和数据库之间的边界&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="副标题--摘要"&gt;副标题 / 摘要&lt;/h3&gt;
&lt;p&gt;很多用 FastAPI/Pydantic 的 Python 工程师，会不知不觉让 Pydantic Model 贯穿 API、业务、数据库所有层。本文用一个清晰的分层思路和完整代码示例，帮你搞清楚：&lt;strong&gt;Pydantic 适合用在什么地方，Domain / ORM 又应该怎么配合。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="目标读者"&gt;目标读者&lt;/h3&gt;
&lt;p&gt;这篇文章适合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正在使用 &lt;strong&gt;FastAPI / Pydantic / SQLAlchemy / SQLModel&lt;/strong&gt; 的 Python 后端工程师&lt;/li&gt;
&lt;li&gt;刚入行 0–3 年、开始关心“分层、架构、领域模型”的开发者&lt;/li&gt;
&lt;li&gt;想从“会写接口”进阶到“懂业务建模、懂分层”的工程师&lt;/li&gt;
&lt;li&gt;对 “Pydantic 要不要进 Domain / 要不要用于 DB 模型” 有疑惑的人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一背景--动机为什么-pydantic-容易长满全项目"&gt;一、背景 / 动机：为什么 Pydantic 容易“长满全项目”？&lt;/h2&gt;
&lt;p&gt;如果你是从 FastAPI 入门后端，很可能经历过这样的路径：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;用 Pydantic 定义请求体、响应体：太好用了，自动校验 + 文档 + 类型提示。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;觉得既然 Pydantic 这么香，那干脆：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直接拿 Pydantic Model 当“业务对象”传来传去&lt;/li&gt;
&lt;li&gt;甚至顺手拿它去做“数据库模型”&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;渐渐地，你的项目变成：&lt;/p&gt;</description></item><item><title>从写路由到写"大脑":Python工程师如何先搞定核心逻辑,再考虑API</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/core-logic-before-api-for-python-engineers/</link><pubDate>Sat, 08 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/core-logic-before-api-for-python-engineers/</guid><description>&lt;h1 id="标题"&gt;&lt;strong&gt;标题&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;从写路由到写“大脑”：Python 工程师如何先搞定核心逻辑，再考虑 API&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="副标题--摘要"&gt;副标题 / 摘要&lt;/h2&gt;
&lt;p&gt;刚入行时，我们常常一上来就写路由、设计接口、想 chat_id / message_id 怎么存，却发现真正的“智力活”——核心逻辑——总是拖到后面。这篇文章带你从「先写接口」的思维，升级到「先写大脑，再接外壳」，并串起来六边形架构、Clean Architecture、DDD 等背后的经典理念。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="目标读者"&gt;目标读者&lt;/h2&gt;
&lt;p&gt;适合这些同学阅读：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1–3 年经验&lt;/strong&gt; 的 Python 后端工程师 / AI 应用开发者&lt;/li&gt;
&lt;li&gt;正在用 &lt;strong&gt;FastAPI / Django / Flask&lt;/strong&gt; 等框架写 API 的工程师&lt;/li&gt;
&lt;li&gt;想从“CRUD 搬砖工”进化为“懂设计、能抽象”的工程师&lt;/li&gt;
&lt;li&gt;对 &lt;strong&gt;六边形架构 / Clean Architecture / DDD&lt;/strong&gt; 有点好奇但没系统看过书的人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id="一背景--动机为什么先写接口会卡死自己"&gt;一、背景 / 动机：为什么“先写接口”会卡死自己？&lt;/h1&gt;
&lt;p&gt;很多刚入行的 Python 工程师（包括你我）会有这样的流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;产品提一个新需求：做一个 AI 聊天功能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打开编辑器，第一反应就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设计 URL：&lt;code&gt;POST /api/chat/send_message&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;开始写 router：&lt;code&gt;@app.post(&amp;quot;/chat/send&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;想 request body 参数长什么样：&lt;code&gt;chat_id / message_id / user_id / content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;想数据库表结构：&lt;code&gt;chats&lt;/code&gt;，&lt;code&gt;messages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写了一堆 API、schema、model、迁移脚本之后，才想起来：
&lt;strong&gt;“那 AI 回复到底是怎么生成的？”&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>从堵塞到异步:为什么上传文件接口不该等文件处理完</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/from-blocking-to-async-file-upload-processing/</link><pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/from-blocking-to-async-file-upload-processing/</guid><description>&lt;h1 id="-从阻塞到异步为什么上传接口不该等文件处理完"&gt;🚀 从阻塞到异步：为什么上传接口不该等文件处理完？&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;—— 用异步任务和状态跟踪构建高性能文件处理系统&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-副标题--摘要"&gt;🧭 副标题 / 摘要&lt;/h2&gt;
&lt;p&gt;在现代 Web 系统中，文件上传只是起点，真正的挑战在于后续的解析、索引和处理。本文带你理解为什么“上传接口不等待处理完成”是现代架构的核心理念，以及如何通过异步任务 + 状态查询实现稳定、可扩展的后台处理系统。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-目标读者"&gt;👥 目标读者&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;有一定 Web 开发经验的工程师（Python/FastAPI/Node.js 等）&lt;/li&gt;
&lt;li&gt;想优化后端性能、提高可扩展性的中级开发者&lt;/li&gt;
&lt;li&gt;对架构设计、异步系统感兴趣的工程师或技术负责人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="-背景--动机"&gt;🎯 背景 / 动机&lt;/h2&gt;
&lt;p&gt;很多初学者写上传接口时会这样做：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@app.post&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/upload&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;upload_file&lt;/span&gt;(file: UploadFile):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parse_and_store(file) &lt;span style="color:#75715e"&gt;# 阻塞操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;completed&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;表面简单，实则隐藏问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⏱ 超时风险高（解析/embedding/OCR可能几分钟）&lt;/li&gt;
&lt;li&gt;🧵 阻塞主线程，拖慢整个 API 服务&lt;/li&gt;
&lt;li&gt;💥 请求中断即任务丢失&lt;/li&gt;
&lt;li&gt;😕 用户只能干等着，无法看到进度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解决方案就是：&lt;strong&gt;上传与处理分离&lt;/strong&gt;。上传只负责“投递任务”，处理由后台 worker 异步执行，状态存储在数据库中供前端查询。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-核心概念"&gt;🔍 核心概念&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;异步任务（Async Job）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;文件解析、OCR、embedding 等耗时操作独立运行，不阻塞主线程。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;任务队列（Task Queue）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;临时存放待执行的任务，如 Redis、RabbitMQ、Celery。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;状态持久化（State Persistence）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;将任务状态（pending / processing / completed / failed）写入数据库。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSE（Server-Sent Events）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一种轻量的实时推送机制，前端可实时接收状态更新。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-实践指南--实现步骤"&gt;⚙️ 实践指南 / 实现步骤&lt;/h2&gt;
&lt;h3 id="1-上传文件接口只负责入队"&gt;1️⃣ 上传文件接口（只负责入队）&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@router.post&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/upload&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;upload&lt;/span&gt;(file: UploadFile, user&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Depends(get_verified_user)):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; file_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;create(file, user&lt;span style="color:#f92672"&gt;.&lt;/span&gt;id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 异步提交任务（Celery、RQ、线程池等）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; background_tasks&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add_task(process_file, file_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;file_id&amp;#34;&lt;/span&gt;: file_id, &lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-异步任务后台-worker-执行"&gt;2️⃣ 异步任务（后台 worker 执行）&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process_file&lt;/span&gt;(file_id: str):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; file &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(file_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;update_status(file_id, &lt;span style="color:#e6db74"&gt;&amp;#34;processing&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parse_and_vectorize(file)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;update_status(file_id, &lt;span style="color:#e6db74"&gt;&amp;#34;completed&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;update_status(file_id, &lt;span style="color:#e6db74"&gt;&amp;#34;failed&amp;#34;&lt;/span&gt;, error&lt;span style="color:#f92672"&gt;=&lt;/span&gt;str(e))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-状态查询接口"&gt;3️⃣ 状态查询接口&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@router.get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{id}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/process/status&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_status&lt;/span&gt;(id: str, stream: bool &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; file &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; stream:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event_stream&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Files&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_status(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;data: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dumps({&lt;span style="color:#e6db74"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;: status})&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; status &lt;span style="color:#f92672"&gt;in&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#34;completed&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;failed&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; StreamingResponse(event_stream(), media_type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text/event-stream&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: file&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="-可运行示例"&gt;💻 可运行示例&lt;/h2&gt;
&lt;h3 id="前端轮询"&gt;前端轮询：&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;checkStatus&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fileId&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;processing&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`/api/files/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;fileId&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/process/status`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;当前状态:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; =&amp;gt; &lt;span style="color:#a6e22e"&gt;setTimeout&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;alert&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;解析完成！&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="前端-sse-实时监听"&gt;前端 SSE 实时监听：&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;evtSource&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EventSource&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`/api/files/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;fileId&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/process/status?stream=true`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;evtSource&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;onmessage&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;文件状态:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;completed&amp;#34;&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;evtSource&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;close&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="-原理解释与取舍"&gt;🧠 原理解释与取舍&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;同步上传+处理&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;实现简单，但阻塞主线程&lt;/td&gt;
&lt;td&gt;小文件、低并发、离线脚本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;异步上传+状态查询（推荐）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;非阻塞、可恢复、可扩展&lt;/td&gt;
&lt;td&gt;Web 应用、后台任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;消息队列驱动&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;支持分布式任务、重试机制&lt;/td&gt;
&lt;td&gt;大规模系统、微服务架构&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;取舍原则：&lt;/p&gt;</description></item><item><title>为什么让前端完成Chat Completion: 一套通用的多模型流式对话架构设计</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/frontend-chat-completion-multimodel-streaming-architecture/</link><pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/frontend-chat-completion-multimodel-streaming-architecture/</guid><description>&lt;h1 id="-为什么让前端执行-chat-completion一套通用的多模型流式对话架构设计"&gt;🔌 为什么让前端执行 Chat Completion：一套通用的多模型流式对话架构设计&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;副标题 / 摘要&lt;/strong&gt;
在现代 AI 聊天系统中，很多人会问：为什么不直接在后端调用 OpenAI API？
本文将带你理解一种更灵活的架构——让前端承担推理执行，后端负责调度和状态同步。适合需要支持多模型、本地推理或用户自带 API Key 的开发者。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标读者&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI 聊天应用开发者&lt;/li&gt;
&lt;li&gt;WebSocket / Socket.IO 实践者&lt;/li&gt;
&lt;li&gt;想构建多模型、多端协作聊天系统的架构师&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="-背景--动机"&gt;🧠 背景 / 动机&lt;/h2&gt;
&lt;p&gt;传统的聊天后端往往直接在服务器调用 OpenAI API：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;chat&lt;span style="color:#f92672"&gt;.&lt;/span&gt;completions&lt;span style="color:#f92672"&gt;.&lt;/span&gt;create(model&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;gpt-4o&amp;#34;&lt;/span&gt;, messages&lt;span style="color:#f92672"&gt;=&lt;/span&gt;messages)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;虽然简单，但带来几个现实问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有请求都消耗服务器的 Key，成本高且难追踪；&lt;/li&gt;
&lt;li&gt;无法支持用户自定义 Key（BYOK 模式）；&lt;/li&gt;
&lt;li&gt;无法连接用户本地推理（如 Ollama、LM Studio）；&lt;/li&gt;
&lt;li&gt;无法切换不同模型或 API Base URL；&lt;/li&gt;
&lt;li&gt;前后端状态不同步，不利于流式消息推送。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了解决这些问题，一些开源系统（如 Open-WebUI、Chatbot-UI 增强版）采用了更灵活的 &lt;strong&gt;Socket.IO 双向通信架构&lt;/strong&gt;。
服务端负责「调度与状态流」，前端负责「执行与回传」。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-核心概念"&gt;🧩 核心概念&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Socket.IO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;基于 WebSocket 的实时双向通信库，支持事件与回调。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;event_emitter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;服务端向前端广播事件（推送消息/状态）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;event_caller (sio.call)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;服务端请求前端执行任务（RPC），并等待前端 callback 返回。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;request:chat:completion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一种自定义事件类型，用于请求前端执行 chat completion。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BYOK 模式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;“Bring Your Own Key”，用户使用自己的 OpenAI Key 调用 API。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Executor 架构&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;前端承担推理任务的执行者，后端作为协调者。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-实践指南--步骤"&gt;🧭 实践指南 / 步骤&lt;/h2&gt;
&lt;h3 id="1-服务端发送调用请求"&gt;1️⃣ 服务端发送调用请求&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; event_caller({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;request:chat:completion&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;form_data&amp;#34;&lt;/span&gt;: form_data,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;: models[form_data[&lt;span style="color:#e6db74"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;channel&amp;#34;&lt;/span&gt;: channel,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;session_id&amp;#34;&lt;/span&gt;: session_id,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;event_caller&lt;/code&gt; 使用 &lt;code&gt;sio.call()&lt;/code&gt; 发送事件给指定客户端，并等待 callback 返回。&lt;/p&gt;</description></item><item><title>webSocket深入理解:为什么要保持一个永远在线的连接</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/websocket-why-keep-alive/</link><pubDate>Fri, 31 Oct 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/websocket-why-keep-alive/</guid><description>&lt;h1 id="-websocket-深入理解为什么要保持一个永远在线的连接"&gt;🛰️ WebSocket 深入理解：为什么要保持一个“永远在线”的连接？&lt;/h1&gt;
&lt;h2 id="-副标题--摘要"&gt;✨ 副标题 / 摘要&lt;/h2&gt;
&lt;p&gt;这篇文章带你彻底搞懂 WebSocket：
它和 HTTP 的根本区别、为什么需要“长连接”、连接是如何建立和保持的、以及它在实时应用中的意义。
适合想从“知道是什么”到“理解为什么”的开发者。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-目标读者"&gt;👩‍💻 目标读者&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Web 前后端初级到中级开发者&lt;/li&gt;
&lt;li&gt;想实现实时聊天、AI 流式输出、协作系统的工程师&lt;/li&gt;
&lt;li&gt;想从 HTTP 模型过渡到实时架构思维的学习者&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="-背景--动机为什么这个问题重要"&gt;🧭 背景 / 动机：为什么这个问题重要？&lt;/h2&gt;
&lt;p&gt;几乎每个现代 Web 应用都涉及“实时”功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;聊天对话（ChatGPT、Slack）&lt;/li&gt;
&lt;li&gt;实时通知（邮箱、消息提醒）&lt;/li&gt;
&lt;li&gt;在线协作（Notion、Google Docs）&lt;/li&gt;
&lt;li&gt;数据看板（实时指标、监控）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然而，传统的 HTTP 是“一问一答”的协议，
无法满足&lt;strong&gt;服务器主动通知客户端&lt;/strong&gt;、&lt;strong&gt;低延迟双向通信&lt;/strong&gt;的需求。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WebSocket 的出现&lt;/strong&gt;，彻底改变了这种单向关系，
让 Web 应用第一次真正拥有了“实时对话”的能力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="-核心概念与术语解释"&gt;🧠 核心概念与术语解释&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一问一答型协议。客户端发请求，服务器回响应，然后断开。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;长连接&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一条保持不关闭的 TCP 连接，可反复收发数据。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一种基于 TCP 的双向通信协议，能让服务器主动推送消息。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;握手 (Handshake)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;客户端通过 HTTP 请求告诉服务器：“我想升级为 WebSocket 协议”。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;帧 (Frame)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WebSocket 传输的最小数据单元，比 HTTP header 更轻量。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;心跳 (Ping/Pong)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;定期发送的小数据包，防止连接超时断开。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-实践指南websocket-建立的全过程"&gt;🪜 实践指南：WebSocket 建立的全过程&lt;/h2&gt;
&lt;p&gt;1️⃣ &lt;strong&gt;浏览器发起请求（HTTP 阶段）&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>从 Pip 到 UV：一站式 Python 包管理与依赖同步指南</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pip-to-uv-python-package-management/</link><pubDate>Fri, 31 Oct 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/pip-to-uv-python-package-management/</guid><description>&lt;h1 id="-从-pip-到-uv一站式-python-包管理与依赖同步指南"&gt;🚀 从 Pip 到 UV：一站式 Python 包管理与依赖同步指南&lt;/h1&gt;
&lt;h2 id="-副标题--摘要"&gt;💡 副标题 / 摘要&lt;/h2&gt;
&lt;p&gt;想让你的 Python 环境更干净、更快、更可靠？本文将带你从传统的 &lt;code&gt;pip + venv + requirements.txt&lt;/code&gt; 迁移到现代的 &lt;code&gt;uv&lt;/code&gt; 包管理系统，并教你如何在两者之间无缝同步。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-目标读者"&gt;🎯 目标读者&lt;/h2&gt;
&lt;p&gt;适合 &lt;strong&gt;Python 开发者&lt;/strong&gt;（初学者到中级）、&lt;strong&gt;数据科学家&lt;/strong&gt;、&lt;strong&gt;后端工程师&lt;/strong&gt;，以及希望提升开发环境一致性、减少依赖地狱的读者。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-背景--动机"&gt;🔥 背景 / 动机&lt;/h2&gt;
&lt;p&gt;在日常 Python 开发中，我们经常遇到以下痛点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;环境混乱、包冲突；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip install&lt;/code&gt; 太慢；&lt;/li&gt;
&lt;li&gt;不同机器、团队成员环境不一致；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requirements.txt&lt;/code&gt; 手动维护麻烦。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而 &lt;strong&gt;uv&lt;/strong&gt; 是一个由 Astral 团队推出的新一代包管理工具，
用 Rust 编写，集成了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包安装（比 pip 快数倍）；&lt;/li&gt;
&lt;li&gt;虚拟环境管理；&lt;/li&gt;
&lt;li&gt;锁文件机制（可复现环境）；&lt;/li&gt;
&lt;li&gt;与 PyPI 完全兼容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话：&lt;strong&gt;uv = pip + virtualenv + pip-tools + poetry 的融合体&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-核心概念"&gt;🧩 核心概念&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pyproject.toml&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;现代 Python 项目的依赖与元信息文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;uv.lock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;锁文件，记录所有依赖的精确版本，保证可复现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;uv sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;根据锁文件同步环境（自动创建/更新虚拟环境）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;uv add / remove&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;添加或删除依赖，并自动更新锁文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;uv export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;导出为 &lt;code&gt;requirements.txt&lt;/code&gt;，兼容传统 pip 流程&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-实践指南--步骤"&gt;🛠 实践指南 / 步骤&lt;/h2&gt;
&lt;h3 id="一从-pip-项目迁移到-uv"&gt;一、从 pip 项目迁移到 uv&lt;/h3&gt;
&lt;p&gt;假设你已有一个项目：&lt;/p&gt;</description></item><item><title>如何高效审核后端fastapi代码</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/efficient-fastapi-code-review/</link><pubDate>Thu, 30 Oct 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/efficient-fastapi-code-review/</guid><description>&lt;h1 id="-如何高效审核-fastapi-后端项目的-pull-requestpr"&gt;🧩 如何高效审核 FastAPI 后端项目的 Pull Request（PR）&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;副标题 / 摘要：&lt;/strong&gt;
本文为你系统梳理了在 Python FastAPI 项目中如何进行专业的代码审核流程，从逻辑正确性到安全、性能与架构一致性，附带实用审查清单与示例，助你成为团队中更高效的 Reviewer。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-目标读者"&gt;👥 目标读者&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;Python + FastAPI&lt;/strong&gt; 的中高级后端开发者&lt;/li&gt;
&lt;li&gt;初入团队、需要学习代码审查流程的工程师&lt;/li&gt;
&lt;li&gt;负责代码质量与合并决策的 Tech Lead / Reviewer&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="-背景与动机"&gt;💡 背景与动机&lt;/h2&gt;
&lt;p&gt;在多人协作的后端项目中，&lt;strong&gt;代码审查（Code Review）&lt;/strong&gt; 是保障系统稳定、提升团队代码质量的关键环节。
但许多工程师在面对 PR 时往往只“浏览一下改动”，忽略了逻辑、性能和安全的隐患。&lt;/p&gt;
&lt;p&gt;尤其在 &lt;strong&gt;FastAPI&lt;/strong&gt; 项目中，接口结构简洁、异步特性突出，但也因此容易出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不当的 &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; 用法导致阻塞；&lt;/li&gt;
&lt;li&gt;不安全的输入校验；&lt;/li&gt;
&lt;li&gt;不一致的 Schema 与返回模型；&lt;/li&gt;
&lt;li&gt;难以维护的业务逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，本文将教你如何 &lt;strong&gt;系统化、标准化地审查 FastAPI PR&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-核心概念"&gt;🧠 核心概念&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PR (Pull Request)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;在 Git 平台上发起代码合并请求，等待他人审核后合并到主分支。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;同事间对代码进行质量和设计审查的过程。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高性能、异步的 Python Web 框架，基于 Pydantic 和 Starlette。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pydantic Schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FastAPI 的数据验证与序列化模型系统。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Depends()&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FastAPI 的依赖注入机制，用于数据库连接、认证等。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-实践指南pr-审核流程"&gt;🧭 实践指南：PR 审核流程&lt;/h2&gt;
&lt;h3 id="1-阅读-pr-描述"&gt;1️⃣ 阅读 PR 描述&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;明确改动目的、功能范围、对应 issue。&lt;/li&gt;
&lt;li&gt;判断是否为修复、功能新增、重构或优化。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-浏览改动文件"&gt;2️⃣ 浏览改动文件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;注意核心目录：&lt;code&gt;routers/&lt;/code&gt;, &lt;code&gt;schemas/&lt;/code&gt;, &lt;code&gt;models/&lt;/code&gt;, &lt;code&gt;services/&lt;/code&gt;, &lt;code&gt;core/&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;检查是否包含依赖变更、配置修改或多余文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-深入逻辑代码"&gt;3️⃣ 深入逻辑代码&lt;/h3&gt;
&lt;p&gt;重点审查：&lt;/p&gt;</description></item><item><title>如何配置orm管理数据</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/configure-orm-data-management/</link><pubDate>Thu, 23 Oct 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/configure-orm-data-management/</guid><description>&lt;h1 id="-从零到生产如何优雅地设计-orm-层管理以-sqlalchemy-为核心"&gt;🧱 从零到生产：如何优雅地设计 ORM 层管理（以 SQLAlchemy 为核心）&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本文将带你从数据库表结构出发，构建一套高内聚、低耦合的 ORM 层架构。
目标：让你的 Flask / FastAPI 项目在数据访问上既简洁又稳健。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一为什么要重视-orm-层设计"&gt;一、为什么要重视 ORM 层设计？&lt;/h2&gt;
&lt;p&gt;很多项目初期只是“先能跑”，直接把 SQL 写在控制器里，但很快就会出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务逻辑和 SQL 混在一起；&lt;/li&gt;
&lt;li&gt;表关系复杂，维护困难；&lt;/li&gt;
&lt;li&gt;想复用查询逻辑很麻烦；&lt;/li&gt;
&lt;li&gt;迁移到别的框架（Flask → FastAPI）代价大。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ORM 层（Object Relational Mapping）是数据库与业务逻辑之间的 &lt;strong&gt;抽象桥梁&lt;/strong&gt;，
一个好的 ORM 层能让你只关心对象，不用反复写 SQL。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二项目场景招标信息数据系统"&gt;二、项目场景：招标信息数据系统&lt;/h2&gt;
&lt;p&gt;我们以一个真实业务为例：
爬取各网站的招标公告，保存为结构化数据，并生成统计看板。&lt;/p&gt;
&lt;h3 id="目标数据库实体"&gt;目标数据库实体&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;表名&lt;/th&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tender_info&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;公告基本信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tender_attachments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;公告及变更文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tender_organization&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;招标机构与联系方式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tender_statistics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每日/月/年统计信息&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三orm-层设计思路"&gt;三、ORM 层设计思路&lt;/h2&gt;
&lt;h3 id="-分层原则"&gt;🧩 分层原则&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;代码位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model 层&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ORM 模型定义，对应数据库表结构&lt;/td&gt;
&lt;td&gt;&lt;code&gt;models.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Repository 层&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;封装 CRUD 逻辑（数据库操作）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;repository.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service 层&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;业务逻辑层（聚合多个仓库逻辑）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;service.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API 层&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;控制器/路由接口&lt;/td&gt;
&lt;td&gt;Flask/FastAPI 视图文件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这种分层让你做到：&lt;/p&gt;</description></item><item><title>结构化日志和追踪</title><link>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/structured-logging-and-tracing/</link><pubDate>Thu, 28 Aug 2025 00:00:00 +0000</pubDate><guid>https://shio-chan-dev.github.io/jeanblog/zh/dev/python/structured-logging-and-tracing/</guid><description>&lt;p&gt;Python 日志与追踪
Python 日志追踪实践
结构化日志与追踪&lt;/p&gt;
&lt;p&gt;副标题/摘要：
结合 logging + OpenTelemetry 实现结构化日志并把 trace_id 注入日志，便于在生产环境串联调用链与定位问题。&lt;/p&gt;
&lt;p&gt;TL;DR：
设置 json 格式日志并通过 OpenTelemetry 在每条日志里注入 trace_id/span_id。关键步骤：安装依赖 → 配置 logging（JSON）→ 配置 TracerProvider → 用 Filter 从当前 span 提取 trace 信息并添加到日志记录中。&lt;/p&gt;
&lt;p&gt;目录&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;背景与动机（为什么需要）&lt;/li&gt;
&lt;li&gt;关键概念与术语解释&lt;/li&gt;
&lt;li&gt;环境与依赖（安装命令）&lt;/li&gt;
&lt;li&gt;逐步实战示例（可直接运行）&lt;/li&gt;
&lt;li&gt;原理与实现要点&lt;/li&gt;
&lt;li&gt;常见问题与注意事项&lt;/li&gt;
&lt;li&gt;最佳实践总结&lt;/li&gt;
&lt;li&gt;结论与下一步建议&lt;/li&gt;
&lt;li&gt;可视化建议&lt;/li&gt;
&lt;li&gt;参考与延伸阅读&lt;/li&gt;
&lt;li&gt;可复制示例代码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;背景与动机（为什么需要）
现代后端服务分布式部署后，单靠文本日志很难把一次请求链路从入口到后端串起来。结构化日志（JSON）便于聚合与查询；而分布式追踪（tracing）给出调用链与 span 信息。二者结合能快速定位延迟与错误根因：日志告诉你“发生了什么”，trace 告诉你“这个请求经过了哪些服务/操作”。&lt;/p&gt;
&lt;p&gt;关键概念与术语解释（简明）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日志（Logging）：程序运行时的事件记录，通常按级别（INFO/ERROR）输出。&lt;/li&gt;
&lt;li&gt;结构化日志：以 JSON 等结构化格式输出，便于机器处理与检索。&lt;/li&gt;
&lt;li&gt;Trace/Span：一次分布式操作（trace）由若干子操作（span）组成，span 含有 trace_id 与 span_id。&lt;/li&gt;
&lt;li&gt;Context Propagation：在不同服务/线程/协程中传递 trace context 以串联调用链。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;环境与依赖（列出安装命令）
推荐环境：Python 3.8+
安装依赖：
pip install python-json-logger opentelemetry-api opentelemetry-sdk&lt;/p&gt;</description></item></channel></rss>