[{"data":1,"prerenderedAt":2236},["ShallowReactive",2],{"blog-taxonomies":3,"blog-stats":507,"post-2026\u002F2026-07-29-fastapi-工程实践随记":1083},[4,11,16,21,25,29,36,39,44,47,52,58,63,66,71,76,80,84,87,93,96,101,105,110,113,117,122,127,132,136,141,143,146,151,155,157,162,165,169,173,176,179,183,186,190,193,197,200,205,208,211,214,219,222,228,234,237,239,245,249,253,256,260,266,271,276,281,283,287,290,293,298,301,304,306,308,311,314,316,318,321,327,332,337,342,347,352,354,356,358,361,365,367,369,371,374,376,378,381,386,390,393,396,400,404,407,411,413,415,417,421,423,425,428,431,433,435,437,439,441,443,446,448,450,454,456,459,461,463,465,467,469,471,473,477,483,486,488,491,493,495,497,499,501,503,505],{"category":5,"tags":6},"笔记",[7,8,9,10],"QQ","插件","LiteLoader","美化",{"category":12,"tags":13},"其他",[14,15,10],"博客","IP签名",{"category":17,"tags":18},"杂谈",[19,20],"互联网","观点",{"category":5,"tags":22},[14,23,24],"GitHub","数据可视化",{"category":5,"tags":26},[14,27,28],"访问统计","不蒜子",{"category":30,"tags":31},"工具",[32,33,34,35],"Python","爬虫","选课脚本","广州大学",{"category":5,"tags":37},[38,5],"数学分析",{"category":40,"tags":41},"算法",[42,40,43],"数据结构","线性表",{"category":40,"tags":45},[42,46],"复习",{"category":5,"tags":48},[49,50,51],"高等代数","线性代数","知识点",{"category":5,"tags":53},[14,54,55,56,57],"Typora","PicGo","图床","Markdown",{"category":30,"tags":59},[60,61,62],"Jupyter","工具配置","中文",{"category":30,"tags":64},[65,61,10],"VSCode",{"category":30,"tags":67},[68,69,70,35],"路由器","OpenWrt","校园网",{"category":12,"tags":72},[73,74,75],"网络协议","Wireshark","DEVP2P",{"category":5,"tags":77},[73,74,78,79],"Lua","解码器",{"category":5,"tags":81},[82,5,83],"常微分方程","数学",{"category":5,"tags":85},[86,5,83],"概率论",{"category":30,"tags":88},[89,90,91,92],"Docker","WSL","Windows","磁盘清理",{"category":30,"tags":94},[32,33,95,35],"课程表",{"category":5,"tags":97},[14,98,99,100],"Hexo","Gitalk","评论系统",{"category":5,"tags":102},[14,103,98,104],"Obsidian","写作工具",{"category":12,"tags":106},[107,108,109],"课题研究","泊松分布","参数估计",{"category":12,"tags":111},[107,108,109,112],"组会",{"category":12,"tags":114},[109,115,108,116],"IM模型","统计学",{"category":17,"tags":118},[119,120,121],"软考","网络规划","信息系统项目管理",{"category":30,"tags":123},[124,125,126],"Arch Linux","AppImage","Linux",{"category":5,"tags":128},[129,130,131],"编程语言","计算机科学","内存管理",{"category":5,"tags":133},[134,135],"Canvas","前端",{"category":5,"tags":137},[14,138,139,140],"GitHub Actions","Vite","部署",{"category":5,"tags":142},[135],{"category":5,"tags":144},[135,145],"系统设计",{"category":5,"tags":147},[135,148,149,150],"安全","XSS","CSRF",{"category":152,"tags":153},"项目",[134,154],"PixiJS",{"category":152,"tags":156},null,{"category":40,"tags":158},[42,159,160,161],"栈","队列","JavaScript",{"category":40,"tags":163},[42,164,161],"哈希表",{"category":5,"tags":166},[167,161,168],"ES6","Set",{"category":40,"tags":170},[40,171,172],"动态规划","背包问题",{"category":40,"tags":174},[40,175],"滑动窗口",{"category":40,"tags":177},[42,178,161],"链表",{"category":40,"tags":180},[40,181,161,182],"ACM","面试",{"category":40,"tags":184},[40,161,185],"数据类型转换",{"category":40,"tags":187},[42,188,178,189],"顺序表","TypeScript",{"category":5,"tags":191},[135,154,134,192],"渲染",{"category":5,"tags":194},[135,195,134,196],"富文本编辑","wangEditor",{"category":40,"tags":198},[199,40,178],"力扣",{"category":5,"tags":201},[202,203,204,91],"DNS","加密","网络安全",{"category":5,"tags":206},[135,207],"Axios",{"category":5,"tags":209},[135,207,189,210],"接口封装",{"category":5,"tags":212},[135,213,145],"JWT",{"category":152,"tags":215},[134,216,217,154,218],"画布","React","项目开发",{"category":17,"tags":220},[221],"日志",{"category":5,"tags":223},[224,225,226,227],"Neo4j","图数据库","中心度分析","PageRank",{"category":12,"tags":229},[230,231,232,233],"论文","机器学习","随机森林","保险欺诈",{"category":5,"tags":235},[236],"人工智能",{"category":5,"tags":238},[231,40,32],{"category":12,"tags":240},[241,242,243,244],"共识算法","最长链","区块链","算法设计",{"category":5,"tags":246},[161,247,248,135],"闭包","作用域",{"category":5,"tags":250},[14,138,251,252],"RSS","自动化",{"category":30,"tags":254},[89,90,91,255],"安装",{"category":30,"tags":257},[258,91,259],"MSYS2","开发环境",{"category":5,"tags":261},[262,263,264,265],"团队协作","Git","Commit规范","代码审查",{"category":30,"tags":267},[268,269,270],"oh-my-posh","终端美化","CLI",{"category":5,"tags":272},[273,274,275],"上下文工程","Agent","RAG",{"category":5,"tags":277},[278,279,280],"前端工程化","技术选型","工程基建",{"category":5,"tags":282},[275],{"category":5,"tags":284},[285,286],"SDD","规格驱动开发",{"category":5,"tags":288},[289],"neovim",{"category":5,"tags":291},[292],"技术写作",{"category":5,"tags":294},[295,32,296,89,297],"FastAPI","后端","工程实践",{"category":152,"tags":299},[274,300],"Harness Engineering",{"category":5,"tags":302},[303],"RFC",{"category":152,"tags":305},[274,300,275],{"category":152,"tags":307},[274],{"category":40,"tags":309},[199,40,161,310],"两数之和",{"category":40,"tags":312},[199,40,161,313],"双指针",{"category":40,"tags":315},[199,40,161,178],{"category":40,"tags":317},[199,40,161,175],{"category":40,"tags":319},[199,40,161,320],"回溯",{"category":5,"tags":322},[323,324,325,326],"分布式处理与计算","Spark","Scala","RDD",{"category":5,"tags":328},[323,329,330,331],"线性回归","逻辑回归","监督学习",{"category":5,"tags":333},[323,334,335,336],"数值优化","牛顿法","收敛性",{"category":5,"tags":338},[323,339,340,341],"PCA","数据降维","正则化",{"category":5,"tags":343},[323,344,345,346],"聚类","层次聚类","K-Means",{"category":5,"tags":348},[323,349,350,351],"随机模拟","统计推断","EM算法",{"category":5,"tags":353},[24],{"category":5,"tags":355},[24],{"category":5,"tags":357},[24],{"category":40,"tags":359},[42,360],"图论",{"category":40,"tags":362},[42,363,364],"树","二叉树",{"category":40,"tags":366},[42,160],{"category":40,"tags":368},[42],{"category":40,"tags":370},[42,159],{"category":40,"tags":372},[42,40,373],"复杂度分析",{"category":40,"tags":375},[42,43,178],{"category":40,"tags":377},[42,43,188],{"category":40,"tags":379},[42,380],"基础概念",{"category":17,"tags":382},[383,384,385],"随记","照片","高中",{"category":17,"tags":387},[383,388,389],"团建","战地",{"category":17,"tags":391},[383,392],"生活",{"category":17,"tags":394},[383,395],"文学",{"category":17,"tags":397},[383,398,399],"SCP","模因",{"category":17,"tags":401},[383,402,403],"画集","艺术",{"category":17,"tags":405},[14,406,383],"写作",{"category":17,"tags":408},[383,409,410],"音乐","摇滚",{"category":17,"tags":412},[383,392],{"category":17,"tags":414},[383,409],{"category":17,"tags":416},[383,35,392],{"category":17,"tags":418},[383,419,420],"同人","手书",{"category":17,"tags":422},[383,392],{"category":17,"tags":424},[383,392],{"category":17,"tags":426},[383,427],"植物",{"category":17,"tags":429},[383,430],"读书",{"category":17,"tags":432},[383,392],{"category":17,"tags":434},[383,392],{"category":17,"tags":436},[383,392],{"category":17,"tags":438},[383,392],{"category":17,"tags":440},[383,392],{"category":17,"tags":442},[383,392],{"category":17,"tags":444},[14,445,383],"一周年",{"category":17,"tags":447},[383,392],{"category":17,"tags":449},[383,392],{"category":17,"tags":451},[35,452,453],"开源组织","SITE-193",{"category":17,"tags":455},[383,392],{"category":17,"tags":457},[383,458],"年末",{"category":17,"tags":460},[383,392],{"category":17,"tags":462},[383,392],{"category":17,"tags":464},[14,17],{"category":17,"tags":466},[383,392],{"category":17,"tags":468},[383,392],{"category":17,"tags":470},[383,392],{"category":17,"tags":472},[383,392],{"category":17,"tags":474},[383,475,476],"游戏","Control",{"category":30,"tags":478},[479,480,481,482],"Quadim","图像处理","四叉树","Rust",{"category":17,"tags":484},[14,485],"上线纪念",{"category":17,"tags":487},[383,395],{"category":30,"tags":489},[203,490],"测试",{"category":17,"tags":492},[383],{"category":17,"tags":494},[383,392],{"category":17,"tags":496},[383],{"category":5,"tags":498},[40],{"category":5,"tags":500},[40],{"category":5,"tags":502},[40],{"category":5,"tags":504},[40],{"category":5,"tags":506},[40],[508,512,516,520,524,528,532,536,540,544,548,552,556,560,564,568,572,576,579,583,587,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646,650,654,658,662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726,730,734,738,742,746,750,754,758,762,766,770,774,778,782,786,790,794,798,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865,869,873,877,881,885,889,893,897,901,904,907,911,915,919,923,927,930,934,938,942,946,950,954,957,961,965,969,973,977,981,984,988,992,996,1000,1004,1007,1011,1015,1019,1023,1027,1031,1035,1039,1043,1047,1051,1055,1059,1063,1067,1071,1075,1079],{"path":509,"words":510,"published":511,"date":156},"\u002Fposts\u002F2024\u002F2024-06-25-liteloaderqqnt",1296,"2024-06-25 11:03:08",{"path":513,"words":514,"published":515,"date":156},"\u002Fposts\u002F2024\u002F2024-06-26-ip签名",0,"2024-06-26 22:53:53",{"path":517,"words":518,"published":519,"date":156},"\u002Fposts\u002F2024\u002F2024-06-29-何加盐中文互联网正在加速崩塌",4853,"2024-06-29 00:47:13",{"path":521,"words":522,"published":523,"date":156},"\u002Fposts\u002F2024\u002F2024-07-12-github-chart",270,"2024-07-12 20:02:50",{"path":525,"words":526,"published":527,"date":156},"\u002Fposts\u002F2024\u002F2024-07-14-访客数统计",192,"2024-07-24 01:34:28",{"path":529,"words":530,"published":531,"date":156},"\u002Fposts\u002F2024\u002F2024-07-26-广大选课脚本",110,"2024-07-26 01:34:28",{"path":533,"words":534,"published":535,"date":156},"\u002Fposts\u002F2024\u002F2024-07-26-数学分析笔记其二",14,"2024-07-26 23:54:02",{"path":537,"words":538,"published":539,"date":156},"\u002Fposts\u002F2024\u002F2024-07-26-数据结构复习其二",12856,"2024-07-26 02:48:08",{"path":541,"words":542,"published":543,"date":156},"\u002Fposts\u002F2024\u002F2024-07-26-数据结构复习相关",32856,"2024-07-26 02:24:14",{"path":545,"words":546,"published":547,"date":156},"\u002Fposts\u002F2024\u002F2024-07-26-线性代数与空间解析几何知识点全汇总",6110,"2024-07-26 03:27:55",{"path":549,"words":550,"published":551,"date":156},"\u002Fposts\u002F2024\u002F2024-07-31-typora-picgo-兰空图床打造markdown写作环境",311,"2024-07-31 12:56:23",{"path":553,"words":554,"published":555,"date":156},"\u002Fposts\u002F2024\u002F2024-09-02-jupyter配置中文",441,"2024-09-02 23:49:05",{"path":557,"words":558,"published":559,"date":156},"\u002Fposts\u002F2024\u002F2024-09-02-自定义vscode背景图片",100,"2024-09-02 23:39:00",{"path":561,"words":562,"published":563,"date":156},"\u002Fposts\u002F2024\u002F2024-09-03-制作gzhu校园网路由器",1423,"2024-09-03 23:43:18",{"path":565,"words":566,"published":567,"date":156},"\u002Fposts\u002F2024\u002F2024-09-26-09-25-2024",136,"2024-09-26 00:49:26",{"path":569,"words":570,"published":571,"date":156},"\u002Fposts\u002F2024\u002F2024-11-04-创建一个网络包解码器分析devp2p协议",1943,"2024-11-04 16:45:19",{"path":573,"words":574,"published":575,"date":156},"\u002Fposts\u002F2024\u002F2025-03-09-2024一些笔记常微分",16,"2024-12-09 22:51:00",{"path":577,"words":574,"published":578,"date":156},"\u002Fposts\u002F2024\u002F2025-03-09-2024一些笔记概率论","2025-03-09 22:51:00",{"path":580,"words":581,"published":582,"date":156},"\u002Fposts\u002F2025\u002F2025-03-01-windows下释放docker占用的wsl空间",164,"2025-03-01 13:54:15",{"path":584,"words":585,"published":586,"date":156},"\u002Fposts\u002F2025\u002F2025-03-01-爬虫实战-爬取广州大学课程表",774,"2025-03-01 14:55:48",{"path":588,"words":530,"published":589,"date":156},"\u002Fposts\u002F2025\u002F2025-04-03-hexo集成gitalk的问题","2025-04-03 14:26:34",{"path":591,"words":592,"published":593,"date":156},"\u002Fposts\u002F2025\u002F2025-09-22-记一次配置obsidian配合hexo写博客",92,"2025-09-22 16:47:52",{"path":595,"words":596,"published":597,"date":156},"\u002Fposts\u002F2025\u002F2025-10-24-10-20课题",204,"2025-10-24 08:15:01",{"path":599,"words":600,"published":601,"date":156},"\u002Fposts\u002F2025\u002F2025-11-02-组会朝花夕拾",501,"2025-11-02 18:56:59",{"path":603,"words":604,"published":605,"date":156},"\u002Fposts\u002F2025\u002F2025-11-05-基于随机加权推断模型im的参数估计算法",481,"2025-11-05 20:51:41",{"path":607,"words":608,"published":609,"date":156},"\u002Fposts\u002F2025\u002F2025-11-09-关于软考高项网规",884,"2025-11-09 19:54:52",{"path":611,"words":612,"published":613,"date":156},"\u002Fposts\u002F2025\u002F2025-11-17-arch-linux运行appimage相关",365,"2025-11-17 09:59:59",{"path":615,"words":616,"published":617,"date":156},"\u002Fposts\u002F2025\u002F2025-11-19-相关概念",2058,"2025-11-19 11:06:20",{"path":619,"words":620,"published":621,"date":156},"\u002Fposts\u002F2025\u002F2025-11-21-web可视化实践canvas",6393,"2025-11-21 14:50:48",{"path":623,"words":624,"published":625,"date":156},"\u002Fposts\u002F2025\u002F2025-11-22-使用-github-actions-自动部署基于vite的项目到-github-pages",272,"2025-11-23 01:59:24",{"path":627,"words":628,"published":629,"date":156},"\u002Fposts\u002F2025\u002F2025-11-22-关于前端包管理器npmpnpmyarn和bun",4890,"2025-11-22 21:21:15",{"path":631,"words":632,"published":633,"date":156},"\u002Fposts\u002F2025\u002F2025-11-23-undo-redo-机制具体实现",7839,"2025-11-23 19:15:00",{"path":635,"words":636,"published":637,"date":156},"\u002Fposts\u002F2025\u002F2025-11-27-前端安全-关于xss与crsf",1866,"2025-11-28 02:05:25",{"path":639,"words":640,"published":641,"date":156},"\u002Fposts\u002F2025\u002F2025-12-05-现代协同-2d-画布编辑器pixijs-v8",4285,"2025-12-05 02:00:16",{"path":643,"words":644,"published":645,"date":156},"\u002Fposts\u002F2025\u002F2025-12-07-杂谈-课题组系统设计",3710,"2025-12-07 21:09:33",{"path":647,"words":648,"published":649,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-javascript中的数组方法与栈stack和队列queue的实现",474,"2025-11-29 16:58:15",{"path":651,"words":652,"published":653,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-关于javascript-实现哈希表",189,"2025-11-27 11:31:39",{"path":655,"words":656,"published":657,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-关于javascript的set方法",332,"2025-12-25 15:26:18",{"path":659,"words":660,"published":661,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-关于动态规划(背包问题为例)",1238,"2025-11-17 18:24:04",{"path":663,"words":664,"published":665,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-关于滑动窗口",712,"2025-11-20 15:04:40",{"path":667,"words":668,"published":669,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-关于链表(javascript)",500,"2025-11-25 13:00:48",{"path":671,"words":672,"published":673,"date":156},"\u002Fposts\u002F2025\u002F2025-12-25-面试算法acm模式构建构建输入输出模板",2051,"2025-12-25 23:09:45",{"path":675,"words":676,"published":677,"date":156},"\u002Fposts\u002F2025\u002F2025-12-26-javascript-数字数组字符串的处理",483,"2025-12-26 11:20:25",{"path":679,"words":680,"published":681,"date":156},"\u002Fposts\u002F2025\u002F2025-12-26-javascripttypescript-的顺序表链表实现",446,"2025-12-26 12:16:12",{"path":683,"words":684,"published":685,"date":156},"\u002Fposts\u002F2025\u002F2025-12-27-前端画布设计vol-1-实现基础yuan素渲染和状态控制",2136,"2025-12-28 01:23:08",{"path":687,"words":688,"published":689,"date":156},"\u002Fposts\u002F2025\u002F2025-12-27-前端画布设计vol-2-实现富文本编辑",1838,"2025-12-28 02:58:47",{"path":691,"words":692,"published":693,"date":156},"\u002Fposts\u002F2025\u002F2025-12-27-算法刷题-关于链表操作",2135,"2025-12-27 16:12:42",{"path":695,"words":696,"published":697,"date":156},"\u002Fposts\u002F2025\u002F2025-12-27-配置dnscrypt-proxy实现加密dns服务windows",1899,"2025-11-15 01:06:37",{"path":699,"words":700,"published":701,"date":156},"\u002Fposts\u002F2025\u002F2025-12-28-前端-关于网络请求xhrajaxfetchaxios",7788,"2025-10-28 15:48:46",{"path":703,"words":704,"published":705,"date":156},"\u002Fposts\u002F2025\u002F2025-12-28-前端-接口封装与请求规范axios为例",2373,"2025-12-29 01:01:18",{"path":707,"words":708,"published":709,"date":156},"\u002Fposts\u002F2025\u002F2025-12-28-前端-身份验证管理-基于-jwt-token-的实现",2709,"2025-11-23 22:04:41",{"path":711,"words":712,"published":713,"date":156},"\u002Fposts\u002F2025\u002F2025-12-29-记canvas画布项目开发",6254,"2025-12-29 04:52:15",{"path":715,"words":716,"published":717,"date":156},"\u002Fposts\u002F2025\u002Fabout-site",116,"2025-03-31 16:44:24",{"path":719,"words":720,"published":721,"date":156},"\u002Fposts\u002F2025\u002F使用neo4j图数据科学库gds进行中心度分析",143,"2025-04-02 23:37:24",{"path":723,"words":724,"published":725,"date":156},"\u002Fposts\u002F2026\u002F2026-01-03-论文实训草稿",10445,"2026-01-03 14:10:49",{"path":727,"words":728,"published":729,"date":156},"\u002Fposts\u002F2026\u002F2026-01-06-人工智能导论",11461,"2026-01-07 03:25:49",{"path":731,"words":732,"published":733,"date":156},"\u002Fposts\u002F2026\u002F2026-01-07-机器学习相关算法",82,"2026-01-08 02:28:13",{"path":735,"words":736,"published":737,"date":156},"\u002Fposts\u002F2026\u002F2026-01-16-关于低复杂度最长链共识算法设计",4466,"2026-01-16 10:33:23",{"path":739,"words":740,"published":741,"date":156},"\u002Fposts\u002F2026\u002F2026-01-19-杂记2026-01-19",5754,"2026-01-20 01:03:26",{"path":743,"words":744,"published":745,"date":156},"\u002Fposts\u002F2026\u002F2026-01-22-github-action-自动同步博客到-github-主页",1069,"2026-01-22 16:31:47",{"path":747,"words":748,"published":749,"date":156},"\u002Fposts\u002F2026\u002F2026-03-01-windows-wsl安装docker",257,"2026-03-01 20:55:31",{"path":751,"words":752,"published":753,"date":156},"\u002Fposts\u002F2026\u002F2026-03-14-关于-msys2",701,"2026-03-15 03:05:04",{"path":755,"words":756,"published":757,"date":156},"\u002Fposts\u002F2026\u002F2026-03-28-团队项目协作规范随记",2419,"2026-03-29 05:33:50",{"path":759,"words":760,"published":761,"date":156},"\u002Fposts\u002F2026\u002F2026-03-29-oh-my-posh配置分享",104,"2026-03-29 16:34:43",{"path":763,"words":764,"published":765,"date":156},"\u002Fposts\u002F2026\u002F2026-06-13-从上下文工程到-agent-harness-engineering",8840,"2026-06-13 21:06:20",{"path":767,"words":768,"published":769,"date":156},"\u002Fposts\u002F2026\u002F2026-07-13-简谈前端基建质量标准与ai友好建设",3814,"2026-07-13 21:06:20",{"path":771,"words":772,"published":773,"date":156},"\u002Fposts\u002F2026\u002F2026-07-15-agentic-rag-系统实践",13357,"2026-07-15 21:06:20",{"path":775,"words":776,"published":777,"date":156},"\u002Fposts\u002F2026\u002F2026-07-18-简谈sdd规范驱动开发-copy",4476,"2026-07-18 11:06:20",{"path":779,"words":780,"published":781,"date":156},"\u002Fposts\u002F2026\u002F2026-07-20-个人neovim配置分享",366,"2026-07-22 13:46:50",{"path":783,"words":784,"published":785,"date":156},"\u002Fposts\u002F2026\u002F2026-07-20-关于技术写作-设计文档",6763,"2026-07-20 13:46:50",{"path":787,"words":788,"published":789,"date":156},"\u002Fposts\u002F2026\u002F2026-07-29-fastapi-工程实践随记",3417,"2026-07-29 04:58:34",{"path":791,"words":792,"published":793,"date":156},"\u002Fposts\u002Frfc_agentic_project\u002F2026-06-19-rfc-liskin_code_agent",3563,"2026-06-19 15:18:20",{"path":795,"words":796,"published":797,"date":156},"\u002Fposts\u002Frfc_agentic_project\u002F2026-07-25-rfc-glushkov_ogas",13461,"2026-07-28 23:46:50",{"path":799,"words":800,"published":793,"date":156},"\u002Fposts\u002Frfc_agentic_project\u002F2026-08-03-adr-ogas-arkhiv",2698,{"path":802,"words":803,"published":804,"date":156},"\u002Fposts\u002Frfc_agentic_project\u002Frag\u002F2026-08-12-arkhiv_rag-vol-1",4366,"2026-08-12 15:18:20",{"path":806,"words":807,"published":808,"date":156},"\u002Fposts\u002Fleetcode\u002F2025-12-25-力扣百题速练javascripttypescriptvol-1",3108,"2025-12-25 13:57:08",{"path":810,"words":811,"published":812,"date":156},"\u002Fposts\u002Fleetcode\u002F2025-12-26-力扣百题速练javascripttypescriptvol-2",3185,"2025-12-26 13:57:08",{"path":814,"words":815,"published":816,"date":156},"\u002Fposts\u002Fleetcode\u002F2025-12-28-力扣百题速练javascripttypescriptvol-3",2533,"2025-12-29 02:15:44",{"path":818,"words":819,"published":820,"date":156},"\u002Fposts\u002Fleetcode\u002F2026-01-17-力扣百题速练javascripttypescriptvol-4",3024,"2026-01-17 22:53:01",{"path":822,"words":823,"published":824,"date":156},"\u002Fposts\u002Fleetcode\u002F2026-01-19-力扣百题速练javascripttypescriptvol-5",293,"2026-01-19 15:15:12",{"path":826,"words":827,"published":828,"date":156},"\u002Fposts\u002F分布式处理与计算\u002F分布式处理与计算spark系统与scala语言",4320,"2026-07-06 14:10:49",{"path":830,"words":831,"published":832,"date":156},"\u002Fposts\u002F分布式处理与计算\u002F分布式处理与计算分类与回归分析",2081,"2026-07-07 12:40:29",{"path":834,"words":835,"published":836,"date":156},"\u002Fposts\u002F分布式处理与计算\u002F分布式处理与计算数值优化方法",2182,"2026-07-07 13:50:29",{"path":838,"words":839,"published":840,"date":156},"\u002Fposts\u002F分布式处理与计算\u002F分布式处理与计算数据降维",1583,"2026-07-07 21:50:29",{"path":842,"words":843,"published":844,"date":156},"\u002Fposts\u002F分布式处理与计算\u002F分布式处理与计算聚类分析",1882,"2026-07-07 16:50:29",{"path":846,"words":847,"published":848,"date":156},"\u002Fposts\u002F分布式处理与计算\u002F分布式处理与计算随机模拟与统计推断",2433,"2026-07-06 22:40:29",{"path":850,"words":851,"published":852,"date":156},"\u002Fposts\u002F数据可视化\u002F2026-06-14-数据可视化-基本图像",10011,"2026-06-14 20:18:20",{"path":854,"words":855,"published":856,"date":156},"\u002Fposts\u002F数据可视化\u002F2026-06-14-数据可视化-概论",10030,"2026-06-14 15:18:20",{"path":858,"words":859,"published":860,"date":156},"\u002Fposts\u002F数据可视化\u002F2026-06-15-数据可视化-可视化库",2032,"2026-06-15 20:18:20",{"path":862,"words":863,"published":864,"date":156},"\u002Fposts\u002F数据结构\u002F2025-09-29-数据结构-图",5451,"2025-04-15 20:48:06",{"path":866,"words":867,"published":868,"date":156},"\u002Fposts\u002F数据结构\u002F2025-09-29-数据结构-树",4211,"2025-04-30 02:19:06",{"path":870,"words":871,"published":872,"date":156},"\u002Fposts\u002F数据结构\u002F2025-09-29-数据结构-队列",921,"2025-04-11 15:30:08",{"path":874,"words":875,"published":876,"date":156},"\u002Fposts\u002F数据结构\u002F关于数据结构的一些想法",456,"2025-04-04 19:13:49",{"path":878,"words":879,"published":880,"date":156},"\u002Fposts\u002F数据结构\u002F数据结构-栈",920,"2025-04-18 00:17:37",{"path":882,"words":883,"published":884,"date":156},"\u002Fposts\u002F数据结构\u002F数据结构-算法复杂度",2153,"2025-04-05 16:09:55",{"path":886,"words":887,"published":888,"date":156},"\u002Fposts\u002F数据结构\u002F数据结构-线性表链表",4572,"2025-04-06 00:24:40",{"path":890,"words":891,"published":892,"date":156},"\u002Fposts\u002F数据结构\u002F数据结构-线性表顺序表",4191,"2025-04-05 22:09:59",{"path":894,"words":895,"published":896,"date":156},"\u002Fposts\u002F数据结构\u002F数据结构-绪论",2881,"2025-04-05 11:31:22",{"path":898,"words":899,"published":156,"date":900},"\u002Fposts\u002F杂记\u002F2023-12-26-2023年12月26日",7,"2023-12-26 23:51:33",{"path":902,"words":899,"published":156,"date":903},"\u002Fposts\u002F杂记\u002F2024-01-01-2024团建","2024-01-01 01:51:27",{"path":905,"words":581,"published":156,"date":906},"\u002Fposts\u002F杂记\u002F2024-01-09-随记","2024-01-09 20:50:35",{"path":908,"words":909,"published":156,"date":910},"\u002Fposts\u002F杂记\u002F2024-03-12-花名",5275,"2024-03-12 16:04:23",{"path":912,"words":913,"published":156,"date":914},"\u002Fposts\u002F杂记\u002F2024-04-15-逆模因",1353,"2024-04-15 00:26:16",{"path":916,"words":917,"published":156,"date":918},"\u002Fposts\u002F杂记\u002F2024-05-03-艺作",27,"2024-05-03 00:01:00",{"path":920,"words":921,"published":156,"date":922},"\u002Fposts\u002F杂记\u002F2024-05-21-关于博客",1720,"2024-02-21 23:21:11",{"path":924,"words":925,"published":156,"date":926},"\u002Fposts\u002F杂记\u002F2024-06-20-dnvrc维克多崔专访-2023",2036,"2024-06-20 00:28:39",{"path":928,"words":760,"published":156,"date":929},"\u002Fposts\u002F杂记\u002F2024-06-25-6月25日随记","2024-06-25 12:46:36",{"path":931,"words":932,"published":156,"date":933},"\u002Fposts\u002F杂记\u002F2024-06-25-歌名-想見星星",148,"2024-06-25 22:47:59",{"path":935,"words":936,"published":156,"date":937},"\u002Fposts\u002F杂记\u002F2024-07-11-桂花岗",6,"2024-07-11 00:28:17",{"path":939,"words":940,"published":156,"date":941},"\u002Fposts\u002F杂记\u002F2024-07-15-rolling-bocci",9,"2024-07-15 21:25:56",{"path":943,"words":944,"published":156,"date":945},"\u002Fposts\u002F杂记\u002F2024-07-22-七月九日",34,"2024-07-09 17:54:23",{"path":947,"words":948,"published":156,"date":949},"\u002Fposts\u002F杂记\u002F2024-07-22-六月随记",1052,"2024-07-22 01:03:19",{"path":951,"words":952,"published":156,"date":953},"\u002Fposts\u002F杂记\u002F2024-07-22-石菖蒲",41,"2024-07-10 01:26:05",{"path":955,"words":514,"published":156,"date":956},"\u002Fposts\u002F杂记\u002F2024-07-23-新书","2024-07-23 01:05:49",{"path":958,"words":959,"published":156,"date":960},"\u002Fposts\u002F杂记\u002F2024-07-29-年前",74,"2024-07-29 23:59:07",{"path":962,"words":963,"published":156,"date":964},"\u002Fposts\u002F杂记\u002F2024-07-30-周边",138,"2024-07-30 02:03:23",{"path":966,"words":967,"published":156,"date":968},"\u002Fposts\u002F杂记\u002F2024-07-31-2024-7-31日",185,"2024-07-31 01:21:52",{"path":970,"words":971,"published":156,"date":972},"\u002Fposts\u002F杂记\u002F2024-08-02-八月",1710,"2024-08-30 00:31:02",{"path":974,"words":975,"published":156,"date":976},"\u002Fposts\u002F杂记\u002F2024-09-01-九月",1484,"2024-09-26 16:30:15",{"path":978,"words":979,"published":156,"date":980},"\u002Fposts\u002F杂记\u002F2024-09-08-2024年9月8日",97,"2024-09-08 13:59:36",{"path":982,"words":514,"published":156,"date":983},"\u002Fposts\u002F杂记\u002F2024-09-19-博客站的一周年","2024-09-18 23:36:24",{"path":985,"words":986,"published":156,"date":987},"\u002Fposts\u002F杂记\u002F2024-10-03-十月",1028,"2024-10-31 23:54:27",{"path":989,"words":990,"published":156,"date":991},"\u002Fposts\u002F杂记\u002F2024-11-04-十一月",2134,"2024-11-30 00:04:39",{"path":993,"words":994,"published":156,"date":995},"\u002Fposts\u002F杂记\u002F2024-11-10-gzhu-site-193",80,"2024-11-10 19:19:12",{"path":997,"words":998,"published":156,"date":999},"\u002Fposts\u002F杂记\u002F2024-12-01-十二月",455,"2024-12-30 23:13:47",{"path":1001,"words":1002,"published":156,"date":1003},"\u002Fposts\u002F杂记\u002F2024-12-21-年末",23,"2024-12-21 23:21:49",{"path":1005,"words":522,"published":156,"date":1006},"\u002Fposts\u002F杂记\u002F2025-01-30-一月","2025-01-30 23:44:41",{"path":1008,"words":1009,"published":156,"date":1010},"\u002Fposts\u002F杂记\u002F2025-03-01-二月",224,"2025-02-28 00:11:08",{"path":1012,"words":1013,"published":156,"date":1014},"\u002Fposts\u002F杂记\u002F2025-03-01-博客杂谈",344,"2025-03-01 16:03:18",{"path":1016,"words":1017,"published":156,"date":1018},"\u002Fposts\u002F杂记\u002F2025-03-05-三月",2090,"2025-03-05 19:17:28",{"path":1020,"words":1021,"published":156,"date":1022},"\u002Fposts\u002F杂记\u002F2025-07-10-七月",361,"2025-07-10 22:04:32",{"path":1024,"words":1025,"published":156,"date":1026},"\u002Fposts\u002F杂记\u002F2025-09-21-九月",781,"2025-09-21 20:53:37",{"path":1028,"words":1029,"published":156,"date":1030},"\u002Fposts\u002F杂记\u002F2025-10-09-十月",591,"2025-10-09 00:35:07",{"path":1032,"words":1033,"published":156,"date":1034},"\u002Fposts\u002F杂记\u002Fcontrol",13,"2023-09-25 13:03:59",{"path":1036,"words":1037,"published":156,"date":1038},"\u002Fposts\u002F杂记\u002Fquadim四叉树图像处理",1094,"2023-10-21 11:27:06",{"path":1040,"words":1041,"published":156,"date":1042},"\u002Fposts\u002F杂记\u002Fsuccess",24,"2023-09-22 15:59:07",{"path":1044,"words":1045,"published":156,"date":1046},"\u002Fposts\u002F杂记\u002F三光年",295,"2023-11-18 04:18:40",{"path":1048,"words":1049,"published":156,"date":1050},"\u002Fposts\u002F杂记\u002F加密测试",961,"2023-11-21 14:24:01",{"path":1052,"words":1053,"published":156,"date":1054},"\u002Fposts\u002F杂记\u002F啊啊啊啊啊啊啊",54,"2023-09-20 17:29:33",{"path":1056,"words":1057,"published":156,"date":1058},"\u002Fposts\u002F杂记\u002F杂谈",124,"2023-09-25 23:11:32",{"path":1060,"words":1061,"published":156,"date":1062},"\u002Fposts\u002F杂记\u002F艹",25,"2023-10-21 17:40:43",{"path":1064,"words":1065,"published":1066,"date":156},"\u002Fposts\u002F神经网络\u002F神经网络与深度学习卷积神经网络",3168,"2026-06-07 11:23:08",{"path":1068,"words":1069,"published":1070,"date":156},"\u002Fposts\u002F神经网络\u002F神经网络与深度学习循环神经网络",2348,"2026-06-07 11:33:08",{"path":1072,"words":1073,"published":1074,"date":156},"\u002Fposts\u002F神经网络\u002F神经网络与深度学习改进学习方法",3360,"2026-06-07 10:13:08",{"path":1076,"words":1077,"published":1078,"date":156},"\u002Fposts\u002F神经网络\u002F神经网络与深度学习深度生成模型",2727,"2026-06-07 11:43:08",{"path":1080,"words":1081,"published":1082,"date":156},"\u002Fposts\u002F神经网络\u002F神经网络与深度学习神经网络基础",3205,"2026-06-07 10:03:08",{"id":1084,"title":1085,"abbrlink":1086,"body":1087,"category":5,"cover":2225,"date":156,"description":1093,"extension":2226,"mathjax":2227,"meta":2228,"navigation":2227,"path":787,"published":789,"readingMinutes":2231,"seo":2232,"stem":2233,"sticky":156,"swiper_index":156,"tags":2234,"updated":156,"words":788,"__hash__":2235},"posts\u002Fposts\u002F2026\u002F2026-07-29-FastAPI 工程实践随记.md","2026-07-29-记 FastAPI 工程实践","10196",{"type":1088,"value":1089,"toc":2191},"minimark",[1090,1094,1099,1278,1281,1284,1287,1297,1307,1400,1408,1410,1413,1419,1423,1426,1441,1444,1450,1456,1557,1581,1584,1587,1604,1609,1639,1642,1645,1648,1651,1657,1659,1662,1668,1671,1679,1682,1685,1691,1693,1695,1698,1701,1704,1710,1717,1719,1722,1728,1731,1738,1741,1748,1765,1771,1777,1782,1785,1788,1794,1796,1799,1802,1819,1822,1849,1855,1858,1865,1875,1878,1886,1892,1897,1903,1906,1909,1916,1922,1925,1932,1938,1940,1945,1951,1977,1983,1987,1990,1998,2001,2019,2025,2027,2030,2105,2108,2119,2125,2127,2135,2141,2144,2147,2150,2158,2161,2169,2172,2175,2182],[1091,1092,1093],"p",{},"简单记录一下用 FastAPI 搭 Python 后端服务时摸索出来的一套做法，从目录怎么分、CRUD 怎么写，到怎么把通用逻辑抽成基类，按实际项目里的演进顺序串下来",[1095,1096,1098],"h1",{"id":1097},"fast-api-最佳实践指南","Fast Api 最佳实践指南",[1100,1101,1102],"ul",{},[1103,1104,1105,1109],"li",{},[1106,1107,1098],"a",{"href":1108},"#fast-api%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E6%8C%87%E5%8D%97",[1100,1110,1111,1117,1123,1143,1169,1205,1272],{},[1103,1112,1113],{},[1106,1114,1116],{"href":1115},"#%E7%9B%AE%E5%BD%95","目录",[1103,1118,1119],{},[1106,1120,1122],{"href":1121},"#%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84","项目结构",[1103,1124,1125,1129],{},[1106,1126,1128],{"href":1127},"#%E5%BC%82%E6%AD%A5%E8%B7%AF%E7%94%B1","异步路由",[1100,1130,1131,1137],{},[1103,1132,1133],{},[1106,1134,1136],{"href":1135},"#io%E5%AF%86%E9%9B%86%E5%9E%8B%E4%BB%BB%E5%8A%A1","I\u002FO 密集型任务",[1103,1138,1139],{},[1106,1140,1142],{"href":1141},"#cpu%E5%AF%86%E9%9B%86%E5%9E%8B%E4%BB%BB%E5%8A%A1","CPU 密集型任务",[1103,1144,1145,1149],{},[1106,1146,1148],{"href":1147},"#pydantic","Pydantic",[1100,1150,1151,1157,1163],{},[1103,1152,1153],{},[1106,1154,1156],{"href":1155},"#%E5%A4%A7%E9%87%8F%E4%BD%BF%E7%94%A8pydantic","大量使用 Pydantic",[1103,1158,1159],{},[1106,1160,1162],{"href":1161},"#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9F%BA%E7%A1%80%E6%A8%A1%E5%9E%8B","自定义基础模型",[1103,1164,1165],{},[1106,1166,1168],{"href":1167},"#%E6%8B%86%E5%88%86pydantic-basesettings","拆分 Pydantic BaseSettings",[1103,1170,1171,1175],{},[1106,1172,1174],{"href":1173},"#%E4%BE%9D%E8%B5%96%E9%A1%B9","依赖项",[1100,1176,1177,1183,1189,1195],{},[1103,1178,1179],{},[1106,1180,1182],{"href":1181},"#%E8%B6%85%E8%B6%8A%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5","超越依赖注入",[1103,1184,1185],{},[1106,1186,1188],{"href":1187},"#%E9%93%BE%E5%BC%8F%E4%BE%9D%E8%B5%96","链式依赖",[1103,1190,1191],{},[1106,1192,1194],{"href":1193},"#%E6%8B%86%E5%88%86%E5%B9%B6%E5%A4%8D%E7%94%A8%E4%BE%9D%E8%B5%96%E9%A1%B9%E4%BE%9D%E8%B5%96%E8%B0%83%E7%94%A8%E4%BC%9A%E8%A2%AB%E7%BC%93%E5%AD%98","拆分并复用依赖项。依赖调用会被缓存",[1103,1196,1197],{},[1106,1198,1200,1201,1174],{"href":1199},"#%E4%BC%98%E5%85%88%E4%BD%BF%E7%94%A8async%E4%BE%9D%E8%B5%96%E9%A1%B9","优先使用",[1202,1203,1204],"code",{},"async",[1103,1206,1207,1210],{},[1106,1208,12],{"href":1209},"#%E5%85%B6%E4%BB%96",[1100,1211,1212,1218,1224,1230,1236,1242,1248,1254,1260,1266],{},[1103,1213,1214],{},[1106,1215,1217],{"href":1216},"#%E9%81%B5%E5%BE%AArest%E8%A7%84%E8%8C%83","遵循 REST 规范",[1103,1219,1220],{},[1106,1221,1223],{"href":1222},"#fastapi%E5%93%8D%E5%BA%94%E5%BA%8F%E5%88%97%E5%8C%96","FastAPI 响应序列化",[1103,1225,1226],{},[1106,1227,1229],{"href":1228},"#%E5%A6%82%E6%9E%9C%E5%BF%85%E9%A1%BB%E4%BD%BF%E7%94%A8%E5%90%8C%E6%AD%A5sdk%E8%AF%B7%E5%9C%A8%E7%BA%BF%E7%A8%8B%E6%B1%A0%E4%B8%AD%E8%BF%90%E8%A1%8C%E5%AE%83","如果必须使用同步 SDK，请在线程池中运行它。",[1103,1231,1232],{},[1106,1233,1235],{"href":1234},"#valueerrors%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%8F%98%E6%88%90pydantic-validationerror","ValueErrors 可能会变成 Pydantic ValidationError",[1103,1237,1238],{},[1106,1239,1241],{"href":1240},"#%E6%96%87%E6%A1%A3","文档",[1103,1243,1244],{},[1106,1245,1247],{"href":1246},"#%E8%BF%81%E7%A7%BB%E5%B7%A5%E5%85%B7alembic","迁移工具 Alembic",[1103,1249,1250],{},[1106,1251,1253],{"href":1252},"#%E8%AE%BE%E7%BD%AE%E6%95%B0%E6%8D%AE%E5%BA%93%E9%94%AE%E5%91%BD%E5%90%8D%E7%BA%A6%E5%AE%9A","设置数据库键命名约定",[1103,1255,1256],{},[1106,1257,1259],{"href":1258},"#sql%E4%BC%98%E5%85%88pydantic%E6%AC%A1%E4%B9%8B","SQL 优先，Pydantic 次之",[1103,1261,1262],{},[1106,1263,1265],{"href":1264},"#%E4%BB%8E%E4%B8%80%E5%BC%80%E5%A7%8B%E5%B0%B1%E8%AE%BE%E7%BD%AE%E5%BC%82%E6%AD%A5%E6%B5%8B%E8%AF%95%E5%AE%A2%E6%88%B7%E7%AB%AF","从一开始就设置异步测试客户端",[1103,1267,1268],{},[1106,1269,1271],{"href":1270},"#%E4%BD%BF%E7%94%A8ruff","使用 ruff",[1103,1273,1274],{},[1106,1275,1277],{"href":1276},"#%E9%A2%9D%E5%A4%96%E9%83%A8%E5%88%86","额外部分",[1279,1280,1122],"h2",{"id":1122},[1091,1282,1283],{},"项目结构有很多种，但最好的结构是一致、直观且没有意外的。",[1091,1285,1286],{},"许多示例项目和教程按文件类型（如 crud、routers、models）划分项目，这种方式对于微服务或范围较小的项目很有效。但是，这种方法并不适合我们这个包含许多领域和模块的单体应用。",[1091,1288,1289,1290,1296],{},"我发现对于这类情况，更具可扩展性和可演进性的结构是受 Netflix 的",[1106,1291,1295],{"href":1292,"rel":1293},"https:\u002F\u002Fgithub.com\u002FNetflix\u002Fdispatch",[1294],"nofollow","Dispatch","启发，并做了一些小修改。",[1298,1299,1304],"pre",{"className":1300,"code":1302,"language":1303},[1301],"language-text","fastapi-project\n├── alembic\u002F\n├── src\n│   ├── auth\n│   │   ├── router.py\n│   │   ├── schemas.py  # pydantic模型\n│   │   ├── models.py  # 数据库模型\n│   │   ├── dependencies.py\n│   │   ├── config.py  # 本地配置\n│   │   ├── constants.py\n│   │   ├── exceptions.py\n│   │   ├── service.py\n│   │   └── utils.py\n│   ├── aws\n│   │   ├── client.py  # 用于外部服务通信的客户端模型\n│   │   ├── schemas.py\n│   │   ├── config.py\n│   │   ├── constants.py\n│   │   ├── exceptions.py\n│   │   └── utils.py\n│   ├── posts\n│   │   ├── router.py\n│   │   ├── schemas.py\n│   │   ├── models.py\n│   │   ├── dependencies.py\n│   │   ├── constants.py\n│   │   ├── exceptions.py\n│   │   ├── service.py\n│   │   └── utils.py\n│   ├── config.py  # 全局配置\n│   ├── models.py  # 全局模型\n│   ├── exceptions.py  # 全局异常\n│   ├── pagination.py  # 全局模块，如分页\n│   ├── database.py  # 数据库连接相关内容\n│   └── main.py\n├── tests\u002F\n│   ├── auth\n│   ├── aws\n│   └── posts\n├── templates\u002F\n│   └── index.html\n├── requirements\n│   ├── base.txt\n│   ├── dev.txt\n│   └── prod.txt\n├── .env\n├── .gitignore\n├── logging.ini\n└── alembic.ini\n","text",[1202,1305,1302],{"__ignoreMap":1306},"",[1308,1309,1310,1331,1397],"ol",{},[1103,1311,1312,1313,1316,1317],{},"将所有领域目录存储在",[1202,1314,1315],{},"src","文件夹中",[1308,1318,1319,1325],{},[1103,1320,1321,1324],{},[1202,1322,1323],{},"src\u002F"," - 应用的最高级别，包含通用模型、配置和常量等。",[1103,1326,1327,1330],{},[1202,1328,1329],{},"src\u002Fmain.py"," - 项目的根文件，用于初始化 FastAPI 应用",[1103,1332,1333,1334],{},"每个包都有自己的路由、模式、模型等。",[1308,1335,1336,1342,1348,1354,1360,1366,1372,1378,1384],{},[1103,1337,1338,1341],{},[1202,1339,1340],{},"router.py"," - 每个模块的核心，包含所有端点",[1103,1343,1344,1347],{},[1202,1345,1346],{},"schemas.py"," - 用于 pydantic 模型",[1103,1349,1350,1353],{},[1202,1351,1352],{},"models.py"," - 用于数据库模型",[1103,1355,1356,1359],{},[1202,1357,1358],{},"service.py"," - 模块特定的业务逻辑",[1103,1361,1362,1365],{},[1202,1363,1364],{},"dependencies.py"," - 路由依赖项",[1103,1367,1368,1371],{},[1202,1369,1370],{},"constants.py"," - 模块特定的常量和错误代码",[1103,1373,1374,1377],{},[1202,1375,1376],{},"config.py"," - 例如环境变量",[1103,1379,1380,1383],{},[1202,1381,1382],{},"utils.py"," - 非业务逻辑函数，例如响应规范化、数据丰富等",[1103,1385,1386,1389,1390,1393,1394],{},[1202,1387,1388],{},"exceptions.py"," - 模块特定的异常，例如",[1202,1391,1392],{},"PostNotFound","、",[1202,1395,1396],{},"InvalidUserData",[1103,1398,1399],{},"当包需要其他包的服务、依赖项或常量时，使用显式的模块名导入",[1298,1401,1406],{"className":1402,"code":1404,"language":1405,"meta":1306},[1403],"language-python","from src.auth import constants as auth_constants\nfrom src.notifications import service as notification_service\nfrom src.posts.constants import ErrorCode as PostsErrorCode  # 以防每个包的constants模块中都有标准的ErrorCode\n","python",[1202,1407,1404],{"__ignoreMap":1306},[1279,1409,1128],{"id":1128},[1091,1411,1412],{},"FastAPI 首先是一个异步框架。它设计用于处理异步 I\u002FO 操作，这也是它如此快速的原因。",[1091,1414,1415,1416,1418],{},"然而，FastAPI 并不限制你只能使用",[1202,1417,1204],{},"路由，开发者也可以使用同步路由。这可能会让初学者误以为它们是一样的，但实际上并非如此。",[1420,1421,1136],"h3",{"id":1422},"io-密集型任务",[1091,1424,1425],{},"在底层，FastAPI 可以有效地处理异步和同步 I\u002FO 操作。",[1100,1427,1428,1431],{},[1103,1429,1430],{},"FastAPI 在线程池中运行同步路由，阻塞的 I\u002FO 操作不会阻止事件循环执行任务。",[1103,1432,1433,1434,1436,1437,1440],{},"如果路由定义为",[1202,1435,1204],{},"，那么它会通过",[1202,1438,1439],{},"await","正常调用，FastAPI 相信你只会执行非阻塞的 I\u002FO 操作。",[1091,1442,1443],{},"需要注意的是，如果你违反了这种信任，在异步路由中执行阻塞操作，事件循环将无法在阻塞操作完成之前运行后续任务。",[1298,1445,1448],{"className":1446,"code":1447,"language":1405,"meta":1306},[1403],"import asyncio\nimport time\n\nfrom fastapi import APIRouter\n\nrouter = APIRouter()\n\n@router.get(\"\u002Fterrible-ping\")\nasync def terrible_ping():\n    time.sleep(10) # 10秒的I\u002FO阻塞操作，整个进程都会被阻塞\n\n    return {\"pong\": True}\n\n@router.get(\"\u002Fgood-ping\")\ndef good_ping():\n    time.sleep(10) # 10秒的I\u002FO阻塞操作，但在单独的线程中运行整个`good_ping`路由\n\n    return {\"pong\": True}\n\n@router.get(\"\u002Fperfect-ping\")\nasync def perfect_ping():\n    await asyncio.sleep(10) # 非阻塞I\u002FO操作\n\n    return {\"pong\": True}\n",[1202,1449,1447],{"__ignoreMap":1306},[1091,1451,1452],{},[1453,1454,1455],"strong",{},"当我们调用时会发生什么：",[1308,1457,1458,1494,1534],{},[1103,1459,1460,1463],{},[1202,1461,1462],{},"GET \u002Fterrible-ping",[1308,1464,1465,1468,1486],{},[1103,1466,1467],{},"FastAPI 服务器接收请求并开始处理",[1103,1469,1470,1471,1474,1475],{},"服务器的事件循环和队列中的所有任务都将等待",[1202,1472,1473],{},"time.sleep()","完成\n",[1308,1476,1477,1483],{},[1103,1478,1479,1480,1482],{},"服务器认为",[1202,1481,1473],{},"不是 I\u002FO 任务，所以会等待它完成",[1103,1484,1485],{},"等待期间，服务器不会接受任何新请求",[1103,1487,1488,1489],{},"服务器返回响应。\n",[1308,1490,1491],{},[1103,1492,1493],{},"响应之后，服务器开始接受新请求",[1103,1495,1496,1499],{},[1202,1497,1498],{},"GET \u002Fgood-ping",[1308,1500,1501,1503,1510,1528],{},[1103,1502,1467],{},[1103,1504,1505,1506,1509],{},"FastAPI 将整个路由",[1202,1507,1508],{},"good_ping","发送到线程池，工作线程将在那里运行该函数",[1103,1511,1512,1513,1515,1516],{},"在",[1202,1514,1508],{},"执行期间，事件循环从队列中选择下一个任务并处理它们（例如接受新请求、调用数据库）\n",[1100,1517,1518,1525],{},[1103,1519,1520,1521,1524],{},"独立于主线程（即我们的 FastAPI 应用），工作线程将等待",[1202,1522,1523],{},"time.sleep","完成。",[1103,1526,1527],{},"同步操作只阻塞子线程，而不是主线程。",[1103,1529,1530,1531,1533],{},"当",[1202,1532,1508],{},"完成工作后，服务器向客户端返回响应",[1103,1535,1536,1539],{},[1202,1537,1538],{},"GET \u002Fperfect-ping",[1308,1540,1541,1543,1549,1552],{},[1103,1542,1467],{},[1103,1544,1545,1546],{},"FastAPI 等待",[1202,1547,1548],{},"asyncio.sleep(10)",[1103,1550,1551],{},"事件循环从队列中选择下一个任务并处理它们（例如接受新请求、调用数据库）",[1103,1553,1530,1554,1556],{},[1202,1555,1548],{},"完成后，服务器完成路由的执行并向客户端返回响应",[1558,1559,1560,1567],"blockquote",{},[1091,1561,1562,1566],{},[1563,1564,1565],"span",{},"!WARNING","\n关于线程池的注意事项：",[1100,1568,1569,1572],{},[1103,1570,1571],{},"线程比协程需要更多资源，因此它们不像异步 I\u002FO 操作那样轻量。",[1103,1573,1574,1575,1580],{},"线程池的线程数量是有限的，也就是说，你可能会耗尽线程，导致应用变慢。",[1106,1576,1579],{"href":1577,"rel":1578},"https:\u002F\u002Fgithub.com\u002FKludex\u002Ffastapi-tips?tab=readme-ov-file#2-be-careful-with-non-async-functions",[1294],"了解更多","（外部链接）",[1420,1582,1142],{"id":1583},"cpu-密集型任务",[1091,1585,1586],{},"第二个需要注意的是，非阻塞的可等待对象或发送到线程池的操作必须是 I\u002FO 密集型任务（例如打开文件、数据库调用、外部 API 调用）。",[1100,1588,1589,1592,1601],{},[1103,1590,1591],{},"等待 CPU 密集型任务（例如繁重的计算、数据处理、视频转码）是没有意义的，因为 CPU 必须工作才能完成这些任务，而 I\u002FO 操作是外部的，服务器在等待这些操作完成时什么也不做，因此它可以处理下一个任务。",[1103,1593,1594,1595,1600],{},"在其他线程中运行 CPU 密集型任务也不是有效的，因为",[1106,1596,1599],{"href":1597,"rel":1598},"https:\u002F\u002Frealpython.com\u002Fpython-gil\u002F",[1294],"GIL（全局解释器锁）","的存在。简而言之，GIL 只允许一个线程同时工作，这使得它对 CPU 任务毫无用处。",[1103,1602,1603],{},"如果你想优化 CPU 密集型任务，你应该将它们发送到另一个进程中的工作节点。",[1091,1605,1606],{},[1453,1607,1608],{},"困惑用户的相关 StackOverflow 问题",[1308,1610,1611,1627,1633],{},[1103,1612,1613,1617],{},[1106,1614,1615],{"href":1615,"rel":1616},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F62976648\u002Farchitecture-flask-vs-fastapi\u002F70309597#70309597",[1294],[1100,1618,1619],{},[1103,1620,1621,1622],{},"在这里你也可以查看",[1106,1623,1626],{"href":1624,"rel":1625},"https:\u002F\u002Fstackoverflow.com\u002Fa\u002F70309597\u002F6927498",[1294],"我的回答",[1103,1628,1629],{},[1106,1630,1631],{"href":1631,"rel":1632},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F65342833\u002Ffastapi-uploadfile-is-slow-compared-to-flask",[1294],[1103,1634,1635],{},[1106,1636,1637],{"href":1637,"rel":1638},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F71516140\u002Ffastapi-runs-api-calls-in-serial-instead-of-parallel-fashion",[1294],[1279,1640,1148],{"id":1641},"pydantic",[1420,1643,1156],{"id":1644},"大量使用-pydantic",[1091,1646,1647],{},"Pydantic 有丰富的功能来验证和转换数据。",[1091,1649,1650],{},"除了常规功能（如带有默认值的必填和非必填字段），Pydantic 还有内置的综合数据处理工具，如正则表达式、枚举、字符串操作、电子邮件验证等。",[1298,1652,1655],{"className":1653,"code":1654,"language":1405,"meta":1306},[1403],"from enum import Enum\nfrom pydantic import AnyUrl, BaseModel, EmailStr, Field\n\nclass MusicBand(str, Enum):\n   AEROSMITH = \"AEROSMITH\"\n   QUEEN = \"QUEEN\"\n   ACDC = \"AC\u002FDC\"\n\nclass UserBase(BaseModel):\n    first_name: str = Field(min_length=1, max_length=128)\n    username: str = Field(min_length=1, max_length=128, pattern=\"^[A-Za-z0-9-_]+$\")\n    email: EmailStr\n    age: int = Field(ge=18)  # 必须大于或等于18\n    favorite_band: MusicBand | None = None  # 只允许输入\"AEROSMITH\"、\"QUEEN\"、\"AC\u002FDC\"值\n    website: AnyUrl | None = None\n",[1202,1656,1654],{"__ignoreMap":1306},[1420,1658,1162],{"id":1162},[1091,1660,1661],{},"拥有一个可控制的全局基础模型允许我们自定义应用中的所有模型。例如，我们可以强制使用标准的 datetime 格式，或者为基础模型的所有子类引入一个通用方法。",[1298,1663,1666],{"className":1664,"code":1665,"language":1405,"meta":1306},[1403],"from datetime import datetime\nfrom typing import Any\nfrom zoneinfo import ZoneInfo\n\nfrom fastapi.encoders import jsonable_encoder\nfrom pydantic import BaseModel, ConfigDict, field_serializer\n\nclass CustomModel(BaseModel):\n    model_config = ConfigDict(populate_by_name=True)\n\n    @field_serializer(\"*\", when_used=\"json\", check_fields=False)\n    def _serialize_datetimes(self, value: Any) -> Any:\n        if isinstance(value, datetime):\n            if value.tzinfo is None:\n                value = value.replace(tzinfo=ZoneInfo(\"UTC\"))\n            return value.strftime(\"%Y-%m-%dT%H:%M:%S%z\")\n        return value\n\n    def serializable_dict(self, **kwargs):\n        \"\"\"返回仅包含可序列化字段的字典。\"\"\"\n        default_dict = self.model_dump()\n\n        return jsonable_encoder(default_dict)\n",[1202,1667,1665],{"__ignoreMap":1306},[1091,1669,1670],{},"在上面的例子中，我们决定创建一个全局基础模型，它：",[1100,1672,1673,1676],{},[1103,1674,1675],{},"将所有 datetime 字段序列化为具有显式时区的标准格式",[1103,1677,1678],{},"提供一个方法来返回仅包含可序列化字段的字典",[1420,1680,1168],{"id":1681},"拆分-pydantic-basesettings",[1091,1683,1684],{},"BaseSettings 是读取环境变量的一项伟大创新，但为整个应用使用单个 BaseSettings 随着时间的推移可能会变得混乱。为了提高可维护性和组织性，我们将 BaseSettings 拆分到不同的模块和领域中。",[1298,1686,1689],{"className":1687,"code":1688,"language":1405,"meta":1306},[1403],"# src.auth.config\nfrom datetime import timedelta\n\nfrom pydantic_settings import BaseSettings\n\nclass AuthConfig(BaseSettings):\n    JWT_ALG: str\n    JWT_SECRET: str\n    JWT_EXP: int = 5  # 分钟\n\n    REFRESH_TOKEN_KEY: str\n    REFRESH_TOKEN_EXP: timedelta = timedelta(days=30)\n\n    SECURE_COOKIES: bool = True\n\nauth_settings = AuthConfig()\n\n# src.config\nfrom pydantic import PostgresDsn, RedisDsn\nfrom pydantic_settings import BaseSettings\n\nfrom src.constants import Environment\n\nclass Config(BaseSettings):\n    DATABASE_URL: PostgresDsn\n    REDIS_URL: RedisDsn\n\n    SITE_DOMAIN: str = \"myapp.com\"\n\n    ENVIRONMENT: Environment = Environment.PRODUCTION\n\n    SENTRY_DSN: str | None = None\n\n    CORS_ORIGINS: list[str]\n    CORS_ORIGINS_REGEX: str | None = None\n    CORS_HEADERS: list[str]\n\n    APP_VERSION: str = \"1.0\"\n\nsettings = Config()\n",[1202,1690,1688],{"__ignoreMap":1306},[1279,1692,1174],{"id":1174},[1420,1694,1182],{"id":1182},[1091,1696,1697],{},"Pydantic 是一个很棒的模式验证器，但对于涉及调用数据库或外部服务的复杂验证，它还不够。",[1091,1699,1700],{},"FastAPI 文档主要将依赖项展示为端点的依赖注入，但它们也非常适合请求验证。",[1091,1702,1703],{},"依赖项可用于根据数据库约束验证数据（例如，检查电子邮件是否已存在、确保找到用户等）。",[1298,1705,1708],{"className":1706,"code":1707,"language":1405,"meta":1306},[1403],"# dependencies.py\nasync def valid_post_id(post_id: UUID4) -> dict[str, Any]:\n    post = await service.get_by_id(post_id)\n    if not post:\n        raise PostNotFound()\n\n    return post\n\n# router.py\n@router.get(\"\u002Fposts\u002F{post_id}\", response_model=PostResponse)\nasync def get_post_by_id(post: dict[str, Any] = Depends(valid_post_id)):\n    return post\n\n@router.put(\"\u002Fposts\u002F{post_id}\", response_model=PostResponse)\nasync def update_post(\n    update_data: PostUpdate,\n    post: dict[str, Any] = Depends(valid_post_id),\n):\n    updated_post = await service.update(id=post[\"id\"], data=update_data)\n    return updated_post\n\n@router.get(\"\u002Fposts\u002F{post_id}\u002Freviews\", response_model=list[ReviewsResponse])\nasync def get_post_reviews(post: dict[str, Any] = Depends(valid_post_id)):\n    post_reviews = await reviews_service.get_by_post_id(post[\"id\"])\n    return post_reviews\n",[1202,1709,1707],{"__ignoreMap":1306},[1091,1711,1712,1713,1716],{},"如果我们没有将数据验证放入依赖项中，我们将不得不为每个端点验证",[1202,1714,1715],{},"post_id","是否存在，并为每个端点编写相同的测试。",[1420,1718,1188],{"id":1188},[1091,1720,1721],{},"依赖项可以使用其他依赖项，避免类似逻辑的代码重复。",[1298,1723,1726],{"className":1724,"code":1725,"language":1405,"meta":1306},[1403],"# dependencies.py\nfrom fastapi.security import OAuth2PasswordBearer\nimport jwt  # PyJWT\nfrom jwt.exceptions import InvalidTokenError\n\nasync def valid_post_id(post_id: UUID4) -> dict[str, Any]:\n    post = await service.get_by_id(post_id)\n    if not post:\n        raise PostNotFound()\n\n    return post\n\nasync def parse_jwt_data(\n    token: str = Depends(OAuth2PasswordBearer(tokenUrl=\"\u002Fauth\u002Ftoken\"))\n) -> dict[str, Any]:\n    try:\n        payload = jwt.decode(token, \"JWT_SECRET\", algorithms=[\"HS256\"])\n    except InvalidTokenError:\n        raise InvalidCredentials()\n\n    return {\"user_id\": payload[\"id\"]}\n\nasync def valid_owned_post(\n    post: dict[str, Any] = Depends(valid_post_id),\n    token_data: dict[str, Any] = Depends(parse_jwt_data),\n) -> dict[str, Any]:\n    if post[\"creator_id\"] != token_data[\"user_id\"]:\n        raise UserNotOwner()\n\n    return post\n\n# router.py\n@router.get(\"\u002Fusers\u002F{user_id}\u002Fposts\u002F{post_id}\", response_model=PostResponse)\nasync def get_user_post(post: dict[str, Any] = Depends(valid_owned_post)):\n    return\n",[1202,1727,1725],{"__ignoreMap":1306},[1420,1729,1194],{"id":1730},"拆分并复用依赖项依赖调用会被缓存",[1091,1732,1733,1734,1737],{},"依赖项可以多次复用，并且它们不会被重新计算——FastAPI 默认在请求的范围内缓存依赖项的结果，也就是说，如果",[1202,1735,1736],{},"valid_post_id","在一个路由中被多次调用，它只会被调用一次。",[1091,1739,1740],{},"了解这一点后，我们可以将依赖项拆分为多个更小的函数，这些函数在更小的领域上运行，并且更容易在其他路由中复用。",[1091,1742,1743,1744,1747],{},"例如，在下面的代码中，我们三次使用",[1202,1745,1746],{},"parse_jwt_data","：",[1308,1749,1750,1755,1760],{},[1103,1751,1752],{},[1202,1753,1754],{},"valid_owned_post",[1103,1756,1757],{},[1202,1758,1759],{},"valid_active_creator",[1103,1761,1762],{},[1202,1763,1764],{},"get_user_post",[1091,1766,1767,1768,1770],{},"但",[1202,1769,1746],{},"只在第一次调用时被调用一次。",[1298,1772,1775],{"className":1773,"code":1774,"language":1405,"meta":1306},[1403],"# dependencies.py\nfrom fastapi import BackgroundTasks\nfrom fastapi.security import OAuth2PasswordBearer\nimport jwt  # PyJWT\nfrom jwt.exceptions import InvalidTokenError\n\nasync def valid_post_id(post_id: UUID4) -> Mapping:\n    post = await service.get_by_id(post_id)\n    if not post:\n        raise PostNotFound()\n\n    return post\n\nasync def parse_jwt_data(\n    token: str = Depends(OAuth2PasswordBearer(tokenUrl=\"\u002Fauth\u002Ftoken\"))\n) -> dict:\n    try:\n        payload = jwt.decode(token, \"JWT_SECRET\", algorithms=[\"HS256\"])\n    except InvalidTokenError:\n        raise InvalidCredentials()\n\n    return {\"user_id\": payload[\"id\"]}\n\nasync def valid_owned_post(\n    post: Mapping = Depends(valid_post_id),\n    token_data: dict = Depends(parse_jwt_data),\n) -> Mapping:\n    if post[\"creator_id\"] != token_data[\"user_id\"]:\n        raise UserNotOwner()\n\n    return post\n\nasync def valid_active_creator(\n    token_data: dict = Depends(parse_jwt_data),\n):\n    user = await users_service.get_by_id(token_data[\"user_id\"])\n    if not user[\"is_active\"]:\n        raise UserIsBanned()\n\n    if not user[\"is_creator\"]:\n       raise UserNotCreator()\n\n    return user\n\n\n# router.py\n@router.get(\"\u002Fusers\u002F{user_id}\u002Fposts\u002F{post_id}\", response_model=PostResponse)\nasync def get_user_post(\n    worker: BackgroundTasks,\n    post: Mapping = Depends(valid_owned_post),\n    user: Mapping = Depends(valid_active_creator),\n):\n    \"\"\"Get post that belong the active user.\"\"\"\n    worker.add_task(notifications_service.send_email, user[\"id\"])\n    return post\n",[1202,1776,1774],{"__ignoreMap":1306},[1420,1778,1200,1780,1174],{"id":1779},"优先使用async依赖项",[1202,1781,1204],{},[1091,1783,1784],{},"FastAPI 同时支持同步和异步依赖项，当你不需要等待任何东西时，很容易会想使用同步依赖项，但这可能不是最佳选择。",[1091,1786,1787],{},"与路由一样，同步依赖项在线程池中运行。这里的线程也有代价和限制，如果只是进行小的非 I\u002FO 操作，这些代价和限制是多余的。",[1091,1789,1790,1580],{},[1106,1791,1579],{"href":1792,"rel":1793},"https:\u002F\u002Fgithub.com\u002FKludex\u002Ffastapi-tips?tab=readme-ov-file#9-your-dependencies-may-be-running-on-threads",[1294],[1279,1795,12],{"id":12},[1420,1797,1217],{"id":1798},"遵循-rest-规范",[1091,1800,1801],{},"开发 RESTful API 可以更轻松地在如下路由中复用依赖项：",[1308,1803,1804,1809,1814],{},[1103,1805,1806],{},[1202,1807,1808],{},"GET \u002Fcourses\u002F:course_id",[1103,1810,1811],{},[1202,1812,1813],{},"GET \u002Fcourses\u002F:course_id\u002Fchapters\u002F:chapter_id\u002Flessons",[1103,1815,1816],{},[1202,1817,1818],{},"GET \u002Fchapters\u002F:chapter_id",[1091,1820,1821],{},"唯一需要注意的是必须在路径中使用相同的变量名：",[1100,1823,1824],{},[1103,1825,1826,1827,1830,1831,1834,1835,1838,1839,1841,1842,1845,1846,1848],{},"如果你有两个端点",[1202,1828,1829],{},"GET \u002Fprofiles\u002F:profile_id","和",[1202,1832,1833],{},"GET \u002Fcreators\u002F:creator_id","，它们都验证给定的",[1202,1836,1837],{},"profile_id","是否存在，但",[1202,1840,1833],{},"还检查该个人资料是否是创作者，那么最好将",[1202,1843,1844],{},"creator_id","路径变量重命名为",[1202,1847,1837],{},"并链接这两个依赖项。",[1298,1850,1853],{"className":1851,"code":1852,"language":1405,"meta":1306},[1403],"# src.profiles.dependencies\nasync def valid_profile_id(profile_id: UUID4) -> Mapping:\n    profile = await service.get_by_id(profile_id)\n    if not profile:\n        raise ProfileNotFound()\n\n    return profile\n\n# src.creators.dependencies\nasync def valid_creator_id(profile: Mapping = Depends(valid_profile_id)) -> Mapping:\n    if not profile[\"is_creator\"]:\n       raise ProfileNotCreator()\n\n    return profile\n\n# src.profiles.router.py\n@router.get(\"\u002Fprofiles\u002F{profile_id}\", response_model=ProfileResponse)\nasync def get_user_profile_by_id(profile: Mapping = Depends(valid_profile_id)):\n    \"\"\"Get profile by id.\"\"\"\n    return profile\n\n# src.creators.router.py\n@router.get(\"\u002Fcreators\u002F{profile_id}\", response_model=ProfileResponse)\nasync def get_user_profile_by_id(\n     creator_profile: Mapping = Depends(valid_creator_id)\n):\n    \"\"\"Get creator's profile by id.\"\"\"\n    return creator_profile\n\n",[1202,1854,1852],{"__ignoreMap":1306},[1420,1856,1223],{"id":1857},"fastapi-响应序列化",[1091,1859,1860,1861,1864],{},"你可能认为可以返回与路由的",[1202,1862,1863],{},"response_model","匹配的 Pydantic 对象来进行一些优化，但你错了。",[1091,1866,1867,1868,1871,1872,1874],{},"FastAPI 首先使用其",[1202,1869,1870],{},"jsonable_encoder","将该 pydantic 对象转换为字典，然后使用你的",[1202,1873,1863],{},"验证数据，最后才将你的对象序列化为 JSON。",[1091,1876,1877],{},"这意味着你的 Pydantic 模型对象会被创建两次：",[1100,1879,1880,1883],{},[1103,1881,1882],{},"第一次，当你显式创建它以从路由返回时。",[1103,1884,1885],{},"第二次，FastAPI 隐式创建它以根据 response_model 验证响应数据。",[1298,1887,1890],{"className":1888,"code":1889,"language":1405,"meta":1306},[1403],"from fastapi import FastAPI\nfrom pydantic import BaseModel, root_validator\n\napp = FastAPI()\n\nclass ProfileResponse(BaseModel):\n    @model_validator(mode=\"after\")\n    def debug_usage(self):\n        print(\"created pydantic model\")\n\n        return self\n\n@app.get(\"\u002F\", response_model=ProfileResponse)\nasync def root():\n    return ProfileResponse()\n\n",[1202,1891,1889],{"__ignoreMap":1306},[1091,1893,1894],{},[1453,1895,1896],{},"日志输出：",[1298,1898,1901],{"className":1899,"code":1900,"language":1303},[1301],"[INFO] [2022-08-28 12:00:00.000000] created pydantic model\n[INFO] [2022-08-28 12:00:00.000020] created pydantic model\n\n",[1202,1902,1900],{"__ignoreMap":1306},[1420,1904,1229],{"id":1905},"如果必须使用同步-sdk请在线程池中运行它",[1091,1907,1908],{},"如果你必须使用一个库与外部服务交互，并且它不是异步的，那么在外部工作线程中进行 HTTP 调用。",[1091,1910,1911,1912,1915],{},"我们可以使用 starlette 中著名的",[1202,1913,1914],{},"run_in_threadpool","。",[1298,1917,1920],{"className":1918,"code":1919,"language":1405,"meta":1306},[1403],"from fastapi import FastAPI\nfrom fastapi.concurrency import run_in_threadpool\nfrom my_sync_library import SyncAPIClient\n\napp = FastAPI()\n\n@app.get(\"\u002F\")\nasync def call_my_sync_library():\n    my_data = await service.get_my_data()\n\n    client = SyncAPIClient()\n    await run_in_threadpool(client.make_request, data=my_data)\n",[1202,1921,1919],{"__ignoreMap":1306},[1420,1923,1235],{"id":1924},"valueerrors-可能会变成-pydantic-validationerror",[1091,1926,1927,1928,1931],{},"如果你在直接面向客户端的 Pydantic 模式中引发",[1202,1929,1930],{},"ValueError","，它将向用户返回一个详细的响应。",[1298,1933,1936],{"className":1934,"code":1935,"language":1405,"meta":1306},[1403],"# src.profiles.schemas\nfrom pydantic import BaseModel, field_validator\n\nclass ProfileCreate(BaseModel):\n    username: str\n\n    @field_validator(\"password\", mode=\"after\")\n    @classmethod\n    def valid_password(cls, password: str) -> str:\n        if not re.match(STRONG_PASSWORD_PATTERN, password):\n            raise ValueError(\n                \"Password must contain at least \"\n                \"one lower character, \"\n                \"one upper character, \"\n                \"digit or \"\n                \"special symbol\"\n            )\n\n        return password\n\n# src.profiles.routes\nfrom fastapi import APIRouter\n\nrouter = APIRouter()\n\n@router.post(\"\u002Fprofiles\")\nasync def get_creator_posts(profile_data: ProfileCreate):\n   pass\n",[1202,1937,1935],{"__ignoreMap":1306},[1420,1939,1241],{"id":1241},[1308,1941,1942],{},[1103,1943,1944],{},"除非你的 API 是公共的，否则默认隐藏文档。只在选定的环境中显式显示它。",[1298,1946,1949],{"className":1947,"code":1948,"language":1405,"meta":1306},[1403],"from fastapi import FastAPI\nfrom starlette.config import Config\n\nconfig = Config(\".env\")  # parse .env file for env variables\n\nENVIRONMENT = config(\"ENVIRONMENT\")  # get current env name\nSHOW_DOCS_ENVIRONMENT = (\"local\", \"staging\")  # explicit list of allowed envs\n\napp_configs = {\"title\": \"My Cool API\"}\nif ENVIRONMENT not in SHOW_DOCS_ENVIRONMENT:\n   app_configs[\"openapi_url\"] = None  # set url for docs as null\n\napp = FastAPI(**app_configs)\n",[1202,1950,1948],{"__ignoreMap":1306},[1308,1952,1953],{},[1103,1954,1955,1956],{},"帮助 FastAPI 生成易于理解的文档\n",[1308,1957,1958,1970],{},[1103,1959,1960,1961,1393,1963,1393,1966,1969],{},"设置",[1202,1962,1863],{},[1202,1964,1965],{},"status_code",[1202,1967,1968],{},"description","等。",[1103,1971,1972,1973,1976],{},"如果模型和状态不同，使用",[1202,1974,1975],{},"responses","路由属性为不同的响应添加文档",[1298,1978,1981],{"className":1979,"code":1980,"language":1405,"meta":1306},[1403],"from fastapi import APIRouter, status\n\nrouter = APIRouter()\n\n@router.post(\n    \"\u002Fendpoints\",\n    response_model=DefaultResponseModel,  # default response pydantic model\n    status_code=status.HTTP_201_CREATED,  # default status code\n    description=\"Description of the well documented endpoint\",\n    tags=[\"Endpoint Category\"],\n    summary=\"Summary of the Endpoint\",\n    responses={\n        status.HTTP_200_OK: {\n            \"model\": OkResponse, # custom pydantic model for 200 response\n            \"description\": \"Ok Response\",\n        },\n        status.HTTP_201_CREATED: {\n            \"model\": CreatedResponse,  # custom pydantic model for 201 response\n            \"description\": \"Creates something from user request\",\n        },\n        status.HTTP_202_ACCEPTED: {\n            \"model\": AcceptedResponse,  # custom pydantic model for 202 response\n            \"description\": \"Accepts request and handles it later\",\n        },\n    },\n)\nasync def documented_route():\n    pass\n",[1202,1982,1980],{"__ignoreMap":1306},[1091,1984,1985],{},[1453,1986,1253],{},[1091,1988,1989],{},"根据数据库的约定显式设置索引命名比使用 sqlalchemy 的默认命名方式更好。",[1298,1991,1996],{"className":1992,"code":1994,"language":1995,"meta":1306},[1993],"language-jsx","from sqlalchemy import MetaData\n\nPOSTGRES_INDEXES_NAMING_CONVENTION = {\n    \"ix\": \"%(column_0_label)s_idx\",\n    \"uq\": \"%(table_name)s_%(column_0_name)s_key\",\n    \"ck\": \"%(table_name)s_%(constraint_name)s_check\",\n    \"fk\": \"%(table_name)s_%(column_0_name)s_fkey\",\n    \"pk\": \"%(table_name)s_pkey\",\n}\nmetadata = MetaData(naming_convention=POSTGRES_INDEXES_NAMING_CONVENTION)\n","jsx",[1202,1997,1994],{"__ignoreMap":1306},[1420,1999,1247],{"id":2000},"迁移工具-alembic",[1308,2002,2003,2006,2009],{},[1103,2004,2005],{},"迁移必须是静态的且可回滚的。如果你的迁移依赖于动态生成的数据，那么确保只有数据本身是动态的，而不是其结构。",[1103,2007,2008],{},"生成具有描述性名称和 slug 的迁移。slug 是必需的，应该解释所做的更改。",[1103,2010,2011,2012,2015,2016],{},"为新迁移设置人类可读的文件模板。我们使用",[1202,2013,2014],{},"date*_*slug*.py","模式，例如",[1202,2017,2018],{},"2022-08-24_post_content_idx.py",[1298,2020,2023],{"className":2021,"code":2022,"language":1303},[1301],"# alembic.ini\nfile_template = %%(year)d-%%(month).2d-%%(day).2d_%%(slug)s\n",[1202,2024,2022],{"__ignoreMap":1306},[1420,2026,1253],{"id":1253},[1091,2028,2029],{},"保持名称的一致性很重要。我们遵循的一些规则：",[1308,2031,2032,2035,2048,2061,2092,2099],{},[1103,2033,2034],{},"小写蛇形命名（lower_case_snake）",[1103,2036,2037,2038,1393,2041,1393,2044,2047],{},"单数形式（例如",[1202,2039,2040],{},"post",[1202,2042,2043],{},"post_like",[1202,2045,2046],{},"user_playlist","）",[1103,2049,2050,2051,1393,2054,1393,2057,1393,2059],{},"用模块前缀对类似的表进行分组，例如",[1202,2052,2053],{},"payment_account",[1202,2055,2056],{},"payment_bill",[1202,2058,2040],{},[1202,2060,2043],{},[1103,2062,2063,2064],{},"在表之间保持一致，但具体命名也可以，例如\n",[1308,2065,2066,2074],{},[1103,2067,2068,2069,2071,2072],{},"在所有表中使用",[1202,2070,1837],{},"，但如果其中一些表只需要作为创作者的个人资料，则使用",[1202,2073,1844],{},[1103,2075,1512,2076,1393,2078,2081,2082,2084,2085,2088,2089],{},[1202,2077,2043],{},[1202,2079,2080],{},"post_view","等抽象表中使用",[1202,2083,1715],{},"，但在相关模块中使用具体命名，如",[1202,2086,2087],{},"chapters.course_id","中的",[1202,2090,2091],{},"course_id",[1103,2093,2094,2095,2098],{},"datetime 类型字段使用",[1202,2096,2097],{},"_at","后缀",[1103,2100,2101,2102,2098],{},"date 类型字段使用",[1202,2103,2104],{},"_date",[1420,2106,1259],{"id":2107},"sql-优先pydantic-次之",[1100,2109,2110,2113,2116],{},[1103,2111,2112],{},"通常，数据库处理数据的速度比 CPython 快得多，也更简洁。",[1103,2114,2115],{},"最好使用 SQL 进行所有复杂的连接和简单的数据操作。",[1103,2117,2118],{},"最好在数据库中为具有嵌套对象的响应聚合 JSON。",[1298,2120,2123],{"className":2121,"code":2122,"language":1405,"meta":1306},[1403],"# src.posts.service\nfrom typing import Any\n\nfrom pydantic import UUID4\nfrom sqlalchemy import desc, func, select, text\nfrom sqlalchemy.sql.functions import coalesce\n\nfrom src.database import database, posts, profiles, post_review, products\n\nasync def get_posts(\n    creator_id: UUID4, *, limit: int = 10, offset: int = 0\n) -> list[dict[str, Any]]:\n    select_query = (\n        select(\n            (\n                posts.c.id,\n                posts.c.slug,\n                posts.c.title,\n                func.json_build_object(\n                   text(\"'id', profiles.id\"),\n                   text(\"'first_name', profiles.first_name\"),\n                   text(\"'last_name', profiles.last_name\"),\n                   text(\"'username', profiles.username\"),\n                ).label(\"creator\"),\n            )\n        )\n        .select_from(posts.join(profiles, posts.c.owner_id == profiles.c.id))\n        .where(posts.c.owner_id == creator_id)\n        .limit(limit)\n        .offset(offset)\n        .group_by(\n            posts.c.id,\n            posts.c.type,\n            posts.c.slug,\n            posts.c.title,\n            profiles.c.id,\n            profiles.c.first_name,\n            profiles.c.last_name,\n            profiles.c.username,\n            profiles.c.avatar,\n        )\n        .order_by(\n            desc(coalesce(posts.c.updated_at, posts.c.published_at, posts.c.created_at))\n        )\n    )\n\n    return await database.fetch_all(select_query)\n\n# src.posts.schemas\nfrom typing import Any\n\nfrom pydantic import BaseModel, UUID4\n\n\nclass Creator(BaseModel):\n    id: UUID4\n    first_name: str\n    last_name: str\n    username: str\n\nclass Post(BaseModel):\n    id: UUID4\n    slug: str\n    title: str\n    creator: Creator\n\n\n# src.posts.router\nfrom fastapi import APIRouter, Depends\n\nrouter = APIRouter()\n\n@router.get(\"\u002Fcreators\u002F{creator_id}\u002Fposts\", response_model=list[Post])\nasync def get_creator_posts(creator: dict[str, Any] = Depends(valid_creator_id)):\n   posts = await service.get_posts(creator[\"id\"])\n\n   return posts\n",[1202,2124,2122],{"__ignoreMap":1306},[1420,2126,1265],{"id":1265},[1091,2128,2129,2130],{},"使用数据库编写集成测试很可能在将来导致混乱的事件循环错误。立即设置异步测试客户端，例如",[1106,2131,2134],{"href":2132,"rel":2133},"https:\u002F\u002Fgithub.com\u002Fencode\u002Fstarlette\u002Fissues\u002F652",[1294],"httpx",[1298,2136,2139],{"className":2137,"code":2138,"language":1405,"meta":1306},[1403],"from typing import AsyncGenerator\n\nimport pytest\nfrom httpx import AsyncClient, ASGITransport\n\nfrom src.main import app  # inited FastAPI app\n\n@pytest.fixture\nasync def client() -> AsyncGenerator[AsyncClient, None]:\n    transport = ASGITransport(app=app)\n    async with AsyncClient(transport=transport, base_url=\"http:\u002F\u002Ftest\") as ac:\n        yield ac\n\n@pytest.mark.asyncio\nasync def test_create_post(client: AsyncClient):\n    resp = await client.post(\"\u002Fposts\")\n\n    assert resp.status_code == 201\n",[1202,2140,2138],{"__ignoreMap":1306},[1091,2142,2143],{},"除非你有同步数据库连接（抱歉？）或者不打算编写集成测试。",[1420,2145,1271],{"id":2146},"使用-ruff",[1091,2148,2149],{},"有了代码检查工具，你可以忘记代码格式化，专注于编写业务逻辑。",[1091,2151,2152,2157],{},[1106,2153,2156],{"href":2154,"rel":2155},"https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff",[1294],"Ruff","是一个“速度极快”的新代码检查工具，它替代了 black、autoflake、isort，并支持 600 多个检查规则。",[1091,2159,2160],{},"使用 pre-commit 钩子是一种流行的最佳实践，但对我们来说，只使用脚本就足够了。",[1298,2162,2167],{"className":2163,"code":2165,"language":2166,"meta":1306},[2164],"language-bash","#!\u002Fbin\u002Fsh -e\nset -x\n\nruff check --fix src\nruff format src\n","bash",[1202,2168,2165],{"__ignoreMap":1306},[1279,2170,12],{"id":2171},"其他-1",[1091,2173,2174],{},"其他大佬的经验和最佳实践",[1091,2176,2177],{},[1106,2178,2181],{"href":2179,"rel":2180},"https:\u002F\u002Fgithub.com\u002Fzhanymkanov\u002Ffastapi-best-practices\u002Fissues",[1294],"issues（问题）",[1091,2183,2184,2185,2190],{},"例如",[1106,2186,2189],{"href":2187,"rel":2188},"https:\u002F\u002Fgithub.com\u002Fzhanymkanov\u002Ffastapi-best-practices\u002Fissues\u002F4",[1294],"lowercase00","详细描述了他们在权限和认证、基于类的服务和视图、任务队列、自定义响应序列化器、使用 dynaconf 进行配置等方面的最佳实践。",{"title":1306,"searchDepth":2192,"depth":2192,"links":2193},4,[2194,2196,2201,2206,2212,2224],{"id":1122,"depth":2195,"text":1122},2,{"id":1128,"depth":2195,"text":1128,"children":2197},[2198,2200],{"id":1422,"depth":2199,"text":1136},3,{"id":1583,"depth":2199,"text":1142},{"id":1641,"depth":2195,"text":1148,"children":2202},[2203,2204,2205],{"id":1644,"depth":2199,"text":1156},{"id":1162,"depth":2199,"text":1162},{"id":1681,"depth":2199,"text":1168},{"id":1174,"depth":2195,"text":1174,"children":2207},[2208,2209,2210,2211],{"id":1182,"depth":2199,"text":1182},{"id":1188,"depth":2199,"text":1188},{"id":1730,"depth":2199,"text":1194},{"id":1779,"depth":2199,"text":1779},{"id":12,"depth":2195,"text":12,"children":2213},[2214,2215,2216,2217,2218,2219,2220,2221,2222,2223],{"id":1798,"depth":2199,"text":1217},{"id":1857,"depth":2199,"text":1223},{"id":1905,"depth":2199,"text":1229},{"id":1924,"depth":2199,"text":1235},{"id":1241,"depth":2199,"text":1241},{"id":2000,"depth":2199,"text":1247},{"id":1253,"depth":2199,"text":1253},{"id":2107,"depth":2199,"text":1259},{"id":1265,"depth":2199,"text":1265},{"id":2146,"depth":2199,"text":1271},{"id":2171,"depth":2195,"text":12},"https:\u002F\u002Fpic1.zhimg.com\u002F70\u002Fv2-0b11c677ca6d0af703bd0ef206cec856_1440w.avis?source=172ae18b&biz_tag=Post","md",true,{"uuid":2229,"slots":2230},"e2b30b80-2ae8-11f1-b16c-5f59290664c1",{},11,{"title":1085,"description":1093},"posts\u002F2026\u002F2026-07-29-FastAPI 工程实践随记",[295,32,296,89,297],"P1WkJnXsbva7DTvL1WNqSH8aa3DE3kZ4JJpFdiY9iWk",1790443282479]