[{"data":1,"prerenderedAt":829},["ShallowReactive",2],{"post:\u002Fposts\u002Fgit\u002Flocal-remote-repository":3,"posts:ordered":527},{"id":4,"title":5,"body":6,"category":510,"cover":511,"date":512,"description":513,"extension":514,"featured":515,"meta":516,"navigation":517,"path":518,"seo":519,"series":520,"seriesOrder":102,"stem":521,"tags":522,"__hash__":526},"posts\u002Fposts\u002Fgit\u002Flocal-remote-repository.md","本地远程仓库的部署",{"type":7,"value":8,"toc":500},"minimark",[9,13,16,21,29,32,43,57,63,69,73,80,132,135,161,182,186,189,210,217,231,234,254,270,273,302,305,308,322,332,335,338,356,359,362,369,387,393,426,432,442,445,448,473,476,479,482,493,496],[10,11,12],"p",{},"上一篇把 Git 的日常操作过了一遍，但那些提交都还留在自己电脑上。这一篇讲怎么把它们\n放到一台自己的机器上——可以是家里闲置的旧笔记本、一台云服务器，或者局域网里的任意\n一台 Linux。",[10,14,15],{},"托管平台（GitHub、Gitee 之类）当然更省事，但自己搭有几个实际好处：代码不出内网、\n不依赖第三方服务的可用性，而且这个过程本身能把 Git 的「远程」到底是什么讲清楚。",[17,18,20],"h2",{"id":19},"先搞清楚远程是什么","先搞清楚「远程」是什么",[10,22,23,24,28],{},"很多人以为远程仓库是某种特殊的东西，其实它和本地仓库是",[25,26,27],"strong",{},"同一种仓库","，只是少了\n一个工作区。",[10,30,31],{},"普通仓库长这样：",[33,34,39],"pre",{"className":35,"code":37,"language":38},[36],"language-text","my-project\u002F\n├── .git\u002F          ← 版本库本身\n├── src\u002F           ← 工作区：你编辑文件的地方\n└── README.md\n","text",[40,41,37],"code",{"__ignoreMap":42},"",[10,44,45,46,49,50,53,54,56],{},"而远程仓库只需要 ",[40,47,48],{},".git"," 里的内容，不需要工作区——反正没人会在这台服务器上直接改文件。\n这种没有工作区的仓库叫",[25,51,52],{},"裸仓库","（bare repository），目录习惯以 ",[40,55,48],{}," 结尾：",[33,58,61],{"className":59,"code":60,"language":38},[36],"my-project.git\u002F    ← 没有工作区，只有版本库\n",[40,62,60],{"__ignoreMap":42},[10,64,65,68],{},[40,66,67],{},"git push"," 做的事，本质就是把本地的新提交对象传到对面那个仓库里，再移动一下对方的\n分支指针。所以对面是什么形态并不重要，重要的是它得是个能接收推送的仓库。",[17,70,72],{"id":71},"服务端建一个裸仓库","服务端：建一个裸仓库",[10,74,75,76,79],{},"假设你有一台能 SSH 登录的机器，地址是 ",[40,77,78],{},"192.168.1.10","。登上去，建一个专门放仓库的目录：",[33,81,85],{"className":82,"code":83,"language":84,"meta":42,"style":42},"language-bash shiki shiki-themes github-light github-dark","ssh user@192.168.1.10\nsudo mkdir -p \u002Fsrv\u002Fgit\nsudo chown -R user:user \u002Fsrv\u002Fgit\n","bash",[40,86,87,100,116],{"__ignoreMap":42},[88,89,92,96],"span",{"class":90,"line":91},"line",1,[88,93,95],{"class":94},"sScJk","ssh",[88,97,99],{"class":98},"sZZnC"," user@192.168.1.10\n",[88,101,103,106,109,113],{"class":90,"line":102},2,[88,104,105],{"class":94},"sudo",[88,107,108],{"class":98}," mkdir",[88,110,112],{"class":111},"sj4cs"," -p",[88,114,115],{"class":98}," \u002Fsrv\u002Fgit\n",[88,117,119,121,124,127,130],{"class":90,"line":118},3,[88,120,105],{"class":94},[88,122,123],{"class":98}," chown",[88,125,126],{"class":111}," -R",[88,128,129],{"class":98}," user:user",[88,131,115],{"class":98},[10,133,134],{},"然后在里面初始化一个裸仓库：",[33,136,138],{"className":82,"code":137,"language":84,"meta":42,"style":42},"cd \u002Fsrv\u002Fgit\ngit init --bare my-project.git\n",[40,139,140,147],{"__ignoreMap":42},[88,141,142,145],{"class":90,"line":91},[88,143,144],{"class":111},"cd",[88,146,115],{"class":98},[88,148,149,152,155,158],{"class":90,"line":102},[88,150,151],{"class":94},"git",[88,153,154],{"class":98}," init",[88,156,157],{"class":111}," --bare",[88,159,160],{"class":98}," my-project.git\n",[10,162,163,166,167,170,171,174,175,174,178,181],{},[40,164,165],{},"--bare"," 是关键，它告诉 Git「这里不需要工作区」。执行完你会看到 ",[40,168,169],{},"my-project.git","\n里面是 ",[40,172,173],{},"HEAD","、",[40,176,177],{},"objects",[40,179,180],{},"refs"," 这些内容，而不是你熟悉的源码文件——这正是期望的结果。",[17,183,185],{"id":184},"客户端把本地仓库连上去","客户端：把本地仓库连上去",[10,187,188],{},"回到你自己的电脑，在项目目录里加一个远程地址：",[33,190,192],{"className":82,"code":191,"language":84,"meta":42,"style":42},"git remote add origin user@192.168.1.10:\u002Fsrv\u002Fgit\u002Fmy-project.git\n",[40,193,194],{"__ignoreMap":42},[88,195,196,198,201,204,207],{"class":90,"line":91},[88,197,151],{"class":94},[88,199,200],{"class":98}," remote",[88,202,203],{"class":98}," add",[88,205,206],{"class":98}," origin",[88,208,209],{"class":98}," user@192.168.1.10:\u002Fsrv\u002Fgit\u002Fmy-project.git\n",[10,211,212,213,216],{},"这一行里的 ",[40,214,215],{},"user@主机:路径"," 就是 SSH 的写法，Git 会直接调用系统的 SSH 去连。确认一下：",[33,218,220],{"className":82,"code":219,"language":84,"meta":42,"style":42},"git remote -v\n",[40,221,222],{"__ignoreMap":42},[88,223,224,226,228],{"class":90,"line":91},[88,225,151],{"class":94},[88,227,200],{"class":98},[88,229,230],{"class":111}," -v\n",[10,232,233],{},"然后推上去：",[33,235,237],{"className":82,"code":236,"language":84,"meta":42,"style":42},"git push -u origin main\n",[40,238,239],{"__ignoreMap":42},[88,240,241,243,246,249,251],{"class":90,"line":91},[88,242,151],{"class":94},[88,244,245],{"class":98}," push",[88,247,248],{"class":111}," -u",[88,250,206],{"class":98},[88,252,253],{"class":98}," main\n",[10,255,256,259,260,263,264,266,267,269],{},[40,257,258],{},"-u"," 会把本地的 ",[40,261,262],{},"main"," 和远程的 ",[40,265,262],{}," 绑定，之后再推就只需要 ",[40,268,67],{},"。",[10,271,272],{},"如果这台机器上原本是个空仓库（比如你从别处克隆的裸仓库），第一次推送前可能要先把\n本地分支的名字对齐：",[33,274,276],{"className":82,"code":275,"language":84,"meta":42,"style":42},"git branch -M main\ngit push -u origin main\n",[40,277,278,290],{"__ignoreMap":42},[88,279,280,282,285,288],{"class":90,"line":91},[88,281,151],{"class":94},[88,283,284],{"class":98}," branch",[88,286,287],{"class":111}," -M",[88,289,253],{"class":98},[88,291,292,294,296,298,300],{"class":90,"line":102},[88,293,151],{"class":94},[88,295,245],{"class":98},[88,297,248],{"class":111},[88,299,206],{"class":98},[88,301,253],{"class":98},[17,303,304],{"id":304},"在另一台机器上克隆",[10,306,307],{},"远程仓库建好之后，别的机器就能像用 GitHub 一样用它：",[33,309,311],{"className":82,"code":310,"language":84,"meta":42,"style":42},"git clone user@192.168.1.10:\u002Fsrv\u002Fgit\u002Fmy-project.git\n",[40,312,313],{"__ignoreMap":42},[88,314,315,317,320],{"class":90,"line":91},[88,316,151],{"class":94},[88,318,319],{"class":98}," clone",[88,321,209],{"class":98},[10,323,324,325,328,329,331],{},"克隆下来的是完整仓库，包含全部历史。之后 ",[40,326,327],{},"git pull"," 拉取别人的改动、",[40,330,67],{}," 推\n自己的改动，和在托管平台上完全一样。",[17,333,334],{"id":334},"用本地路径也能连",[10,336,337],{},"如果服务端和客户端在同一台机器上（比如你想用一个目录来当\"远程\"备份），SSH 那一段\n可以直接换成路径：",[33,339,341],{"className":82,"code":340,"language":84,"meta":42,"style":42},"git remote add origin \u002Fsrv\u002Fgit\u002Fmy-project.git\n",[40,342,343],{"__ignoreMap":42},[88,344,345,347,349,351,353],{"class":90,"line":91},[88,346,151],{"class":94},[88,348,200],{"class":98},[88,350,203],{"class":98},[88,352,206],{"class":98},[88,354,355],{"class":98}," \u002Fsrv\u002Fgit\u002Fmy-project.git\n",[10,357,358],{},"对 Git 来说两者没有本质区别——它只关心\"能不能访问到那个仓库\"。这个技巧在做实验、\n写教程时很方便，不用真的准备两台机器。",[17,360,361],{"id":361},"多人与权限",[10,363,364,365,368],{},"上面用的都是同一个 ",[40,366,367],{},"user"," 账号，多人协作时这样并不合适：谁推的提交都显示成同一个\n作者。有两种常见做法：",[10,370,371,374,375,378,379,382,383,386],{},[25,372,373],{},"建一个专用的 git 账号。"," 所有人用 ",[40,376,377],{},"git@host:..."," 访问，各自通过 SSH 公钥认证，\n真实身份靠提交里的 ",[40,380,381],{},"user.name"," \u002F ",[40,384,385],{},"user.email"," 区分。这是最常用的方式。",[10,388,389,392],{},[25,390,391],{},"用共享组。"," 把仓库目录的属组设成一个公共组，组内成员都有写权限：",[33,394,396],{"className":82,"code":395,"language":84,"meta":42,"style":42},"sudo chgrp -R devs \u002Fsrv\u002Fgit\u002Fmy-project.git\nsudo chmod -R g+ws \u002Fsrv\u002Fgit\u002Fmy-project.git\n",[40,397,398,412],{"__ignoreMap":42},[88,399,400,402,405,407,410],{"class":90,"line":91},[88,401,105],{"class":94},[88,403,404],{"class":98}," chgrp",[88,406,126],{"class":111},[88,408,409],{"class":98}," devs",[88,411,355],{"class":98},[88,413,414,416,419,421,424],{"class":90,"line":102},[88,415,105],{"class":94},[88,417,418],{"class":98}," chmod",[88,420,126],{"class":111},[88,422,423],{"class":98}," g+ws",[88,425,355],{"class":98},[10,427,428,431],{},[40,429,430],{},"g+s"," 让新建的文件自动继承目录的属组，避免新推上来的对象权限不对。",[10,433,434,435,438,439,269],{},"不管用哪种方式，都建议了解一下 ",[40,436,437],{},"git config --system core.sharedRepository","，它能\n让 Git 自动为新对象设置正确的权限位，省掉很多手工 ",[40,440,441],{},"chmod",[17,443,444],{"id":444},"想更完整一点",[10,446,447],{},"如果只是自己用，上面的内容已经够了。想让这台机器更像一个\"服务\"，可以再考虑：",[449,450,451,463,470],"ul",{},[452,453,454,455,458,459,462],"li",{},"用 ",[40,456,457],{},"authorized_keys"," 配合 ",[40,460,461],{},"command="," 限制某个公钥只能做 Git 操作，不能开 shell",[452,464,465,466,469],{},"加 ",[40,467,468],{},"post-receive"," 钩子，推送后自动部署到网站目录",[452,471,472],{},"装个 Gitea 或 Forgejo，得到一个带界面的自托管平台",[10,474,475],{},"这些都属于\"锦上添花\"，不影响 Git 本身的工作方式。",[17,477,478],{"id":478},"什么时候还是用托管平台",[10,480,481],{},"自己搭适合内网项目、私有代码和对可控性有要求的场景。但如果需要：",[449,483,484,487,490],{},[452,485,486],{},"让外部的人参与协作",[452,488,489],{},"不折腾服务器和备份",[452,491,492],{},"用现成的 Issue、CI、代码评审",[10,494,495],{},"那托管平台依然是更省事的选择。两者也不冲突——完全可以主力代码放平台，\n私密的小项目自己搭。",[497,498,499],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":42,"searchDepth":118,"depth":118,"links":501},[502,503,504,505,506,507,508,509],{"id":19,"depth":102,"text":20},{"id":71,"depth":102,"text":72},{"id":184,"depth":102,"text":185},{"id":304,"depth":102,"text":304},{"id":334,"depth":102,"text":334},{"id":361,"depth":102,"text":361},{"id":444,"depth":102,"text":444},{"id":478,"depth":102,"text":478},"工具",null,"2026-10-04","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。","md",false,{},true,"\u002Fposts\u002Fgit\u002Flocal-remote-repository",{"title":5,"description":513},"Git 入门","posts\u002Fgit\u002Flocal-remote-repository",[523,524,525],"Git","服务器","部署","7SY90YM_kOdbFn977uRVUood7OeM7zOGw-AsbWz3ifg",[528,535,541,547,553,559,565,571,577,583,589,595,601,607,613,619,625,631,637,643,649,655,661,667,673,679,684,689,694,700,704,708,712,716,720,724,728,732,736,740,744,750,754,760,764,768,772,776,780,784,788,793,797,801,806,811,815,816,821,825],{"path":529,"title":530,"description":531,"date":532,"series":533,"seriesOrder":534},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19","软件分析与设计笔记",28,{"path":536,"title":537,"description":538,"date":539,"series":533,"seriesOrder":540},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":542,"title":543,"description":544,"date":545,"series":533,"seriesOrder":546},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":548,"title":549,"description":550,"date":551,"series":533,"seriesOrder":552},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":554,"title":555,"description":556,"date":557,"series":533,"seriesOrder":558},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":560,"title":561,"description":562,"date":563,"series":533,"seriesOrder":564},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",23,{"path":566,"title":567,"description":568,"date":569,"series":533,"seriesOrder":570},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",22,{"path":572,"title":573,"description":574,"date":575,"series":533,"seriesOrder":576},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",21,{"path":578,"title":579,"description":580,"date":581,"series":533,"seriesOrder":582},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",20,{"path":584,"title":585,"description":586,"date":587,"series":533,"seriesOrder":588},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",19,{"path":590,"title":591,"description":592,"date":593,"series":533,"seriesOrder":594},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",18,{"path":596,"title":597,"description":598,"date":599,"series":533,"seriesOrder":600},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",17,{"path":602,"title":603,"description":604,"date":605,"series":533,"seriesOrder":606},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",16,{"path":608,"title":609,"description":610,"date":611,"series":533,"seriesOrder":612},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",15,{"path":614,"title":615,"description":616,"date":617,"series":533,"seriesOrder":618},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",14,{"path":620,"title":621,"description":622,"date":623,"series":533,"seriesOrder":624},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",13,{"path":626,"title":627,"description":628,"date":629,"series":533,"seriesOrder":630},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",12,{"path":632,"title":633,"description":634,"date":635,"series":533,"seriesOrder":636},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",11,{"path":638,"title":639,"description":640,"date":641,"series":533,"seriesOrder":642},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",10,{"path":644,"title":645,"description":646,"date":647,"series":533,"seriesOrder":648},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",9,{"path":650,"title":651,"description":652,"date":653,"series":533,"seriesOrder":654},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",8,{"path":656,"title":657,"description":658,"date":659,"series":533,"seriesOrder":660},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",7,{"path":662,"title":663,"description":664,"date":665,"series":533,"seriesOrder":666},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",6,{"path":668,"title":669,"description":670,"date":671,"series":533,"seriesOrder":672},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",5,{"path":674,"title":675,"description":676,"date":677,"series":533,"seriesOrder":678},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",4,{"path":680,"title":681,"description":682,"date":683,"series":533,"seriesOrder":118},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":685,"title":686,"description":687,"date":688,"series":533,"seriesOrder":102},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":690,"title":691,"description":692,"date":693,"series":533,"seriesOrder":91},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",{"path":695,"title":696,"description":697,"date":698,"series":699,"seriesOrder":91},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":701,"title":702,"description":703,"date":698,"series":699,"seriesOrder":102},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":705,"title":706,"description":707,"date":698,"series":699,"seriesOrder":118},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":709,"title":710,"description":711,"date":698,"series":699,"seriesOrder":678},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":713,"title":714,"description":715,"date":698,"series":699,"seriesOrder":672},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":717,"title":718,"description":719,"date":698,"series":699,"seriesOrder":666},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":721,"title":722,"description":723,"date":698,"series":699,"seriesOrder":660},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":725,"title":726,"description":727,"date":698,"series":699,"seriesOrder":654},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":729,"title":730,"description":731,"date":698,"series":699,"seriesOrder":648},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":733,"title":734,"description":735,"date":698,"series":699,"seriesOrder":642},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":737,"title":738,"description":739,"date":698,"series":699,"seriesOrder":636},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":741,"title":742,"description":743,"date":698,"series":699,"seriesOrder":630},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":745,"title":746,"description":747,"date":748,"series":749,"seriesOrder":678},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":751,"title":752,"description":753,"date":748,"series":749,"seriesOrder":118},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":755,"title":756,"description":757,"date":758,"series":759,"seriesOrder":678},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":761,"title":762,"description":763,"date":758,"series":759,"seriesOrder":672},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":765,"title":766,"description":767,"date":758,"series":759,"seriesOrder":666},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":769,"title":770,"description":771,"date":758,"series":759,"seriesOrder":660},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":773,"title":774,"description":775,"date":758,"series":759,"seriesOrder":654},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":777,"title":778,"description":779,"date":758,"series":759,"seriesOrder":648},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":781,"title":782,"description":783,"date":758,"series":759,"seriesOrder":642},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":785,"title":786,"description":787,"date":758,"series":759,"seriesOrder":636},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":789,"title":790,"description":791,"date":792,"series":759,"seriesOrder":91},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":794,"title":795,"description":796,"date":792,"series":759,"seriesOrder":102},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":798,"title":799,"description":800,"date":792,"series":759,"seriesOrder":118},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":802,"title":803,"description":804,"date":805,"series":749,"seriesOrder":102},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":807,"title":808,"description":809,"date":810,"series":749,"seriesOrder":91},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":812,"title":813,"description":814,"date":512,"series":520,"seriesOrder":91},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。",{"path":518,"title":5,"description":513,"date":512,"series":520,"seriesOrder":102},{"path":817,"title":818,"description":819,"date":512,"series":820,"seriesOrder":91},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":822,"title":823,"description":824,"date":512,"series":820,"seriesOrder":102},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":826,"title":827,"description":828,"date":512,"series":820,"seriesOrder":118},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268151533]