[{"data":1,"prerenderedAt":4699},["ShallowReactive",2],{"blog-ai-native-simulation-first-bilingual":3,"blog-adjacent-ai-native-simulation-first-bilingual":1535,"blog-related-ai-native-simulation-first-bilingual":3160},{"id":4,"title":5,"body":6,"category":1513,"cover":1514,"date":1515,"description":1516,"draft":1517,"extension":1518,"meta":1519,"navigation":1520,"path":1521,"readingTime":1522,"seo":1523,"stem":1524,"tags":1525,"__hash__":1534},"blogs\u002Fblogs\u002Fai-native-simulation-first-bilingual.md","AI-Native Software Engineering: Let the System Run Virtually Before Writing the First Line of Code",{"type":7,"value":8,"toc":1491},"minimark",[9,41,50,53,58,61,72,79,82,101,107,110,114,120,127,134,141,144,149,152,156,162,165,168,173,178,181,187,202,206,209,215,221,228,442,448,452,455,461,481,494,499,510,514,520,526,532,538,553,560,567,571,577,580,586,593,596,628,635,640,644,647,653,659,669,679,685,689,692,698,701,707,713,719,725,729,735,738,752,759,765,768,774,776,781,783,785,790,818,825,827,831,834,840,847,850,867,873,876,880,886,893,900,907,910,915,918,922,928,931,934,939,944,947,953,968,972,975,981,987,994,1155,1161,1165,1168,1174,1194,1205,1210,1221,1225,1231,1237,1243,1249,1260,1267,1274,1278,1284,1287,1293,1300,1303,1335,1342,1347,1351,1354,1360,1366,1376,1385,1391,1395,1398,1404,1407,1413,1419,1425,1431,1435,1441,1444,1458,1465,1471,1474,1480,1482,1487],[10,11,12,36],"blockquote",{},[13,14,15,19,20,23,24,27,28,31,32,35],"p",{},[16,17,18],"strong",{},"Core idea:"," Future software development will not revolve around documents, nor around code, but around a ",[16,21,22],{},"Dynamic Project Tree",". Ideas are captured by voice and grow into a tree-shaped knowledge base; every node is first implemented as a ",[16,25,26],{},"virtual function","; virtual functions are wired along the tree into a ",[16,29,30],{},"virtual system","; ",[16,33,34],{},"data-flow simulation"," runs on that virtual system; and only after every simulation passes does real programming begin.",[13,37,38],{},[16,39,40],{},"Code is Output. Model is Truth. Voice is the Beginning. Simulation is the Proof.",[10,42,43],{},[13,44,45,46],{},"📖 ",[47,48,49],"em",{},"中文版在下方 — scroll down for the Chinese version.",[51,52],"hr",{},[54,55,57],"h2",{"id":56},"_1-ai-writes-code-faster-and-faster-software-isnt-getting-faster","1. AI writes code faster and faster — software isn't getting faster",[13,59,60],{},"Today's mainstream AI coding tools — Copilot, Codex, Claude Code — are all essentially embedded in a pipeline designed decades ago:",[62,63,68],"pre",{"className":64,"code":66,"language":67},[65],"language-text","Requirements → Documents → Prototype → Development → Testing → Deployment\n","text",[69,70,66],"code",{"__ignoreMap":71},"",[13,73,74,75,78],{},"That pipeline was born in an era ",[16,76,77],{},"without AI",". AI was simply plugged into the \"development\" stage, making the code-writing step faster.",[13,80,81],{},"But any engineer who has shipped a real project knows that writing code was never the slowest part. What actually burns time is:",[83,84,85,89,92,95,98],"ul",{},[86,87,88],"li",{},"Requirements churn early on; you finish the code only to discover you understood them wrong",[86,90,91],{},"Architectural changes ripple everywhere, and the cost of change grows exponentially with progress",[86,93,94],{},"Documentation forever lags the code, and nobody trusts it",[86,96,97],{},"Multiple AI agents each read the code on their own, with no shared understanding of the project",[86,99,100],{},"Critical design rationale lives scattered across meetings, chat logs, and someone's head",[13,102,103,104],{},"Making the \"write code\" step ten times faster helps with none of these. ",[16,105,106],{},"Errors still stay hidden until integration, joint debugging, or production — and by then, the cost of fixing them has grown by orders of magnitude.",[13,108,109],{},"So what truly deserves to be rebuilt is not the AI. It's the software engineering process itself.",[54,111,113],{"id":112},"_2-hardware-answered-this-long-ago-nobody-tapes-out-without-simulation","2. Hardware answered this long ago: nobody tapes out without simulation",[13,115,116,117],{},"The chip industry has an iron law: ",[16,118,119],{},"you must simulate before tape-out.",[13,121,122,123,126],{},"A single tape-out costs millions of dollars and months of lead time. No team would dare send an unverified design to the fab. That's why the heart of the EDA toolchain has never been \"draw circuits faster\" — it is: make the entire chip ",[16,124,125],{},"run"," in a virtual environment first. Timing, power, signal integrity — everything is verified in the virtual world before anything enters the physical one.",[13,128,129,130,133],{},"Now look at software: we essentially ",[16,131,132],{},"tape out naked",".",[13,135,136,137,140],{},"The requirements document gets written (or not even finished), real code starts immediately, and then integration and testing are left to slam every design error out in the \"physical world.\" We tolerate this only because software's \"re-tape-out\" looks cheap — just change the code, right? But in the AI era, when agents can generate tens of thousands of lines overnight, ",[16,138,139],{},"the cost of rework is precisely what gets amplified",": if the direction is wrong, the faster you generate, the more you waste.",[13,142,143],{},"AI-era software engineering should converge toward hardware engineering:",[13,145,146],{},[16,147,148],{},"Build the virtual system first → complete simulation and verification → then do the real implementation.",[13,150,151],{},"That is the full process this article lays out.",[54,153,155],{"id":154},"_3-voice-is-the-first-input-thought-capture-not-faster-typing","3. Voice is the first input: thought capture, not faster typing",[13,157,158,159,133],{},"The process does not start with a document. It starts with ",[16,160,161],{},"speaking",[13,163,164],{},"Human thinking is not linear. Insights arrive in a flash: a feature idea while walking, the realization in the shower that two modules' dependency is backwards. If capturing that moment requires opening Word, recalling a template, and composing sentences, the thought is long gone.",[13,166,167],{},"The value of voice is not input speed. It is this:",[13,169,170],{},[16,171,172],{},"It captures the genuine ideas inside a thought process with the lowest possible friction.",[10,174,175],{},[13,176,177],{},"Voice = Thought Capture",[13,179,180],{},"The future starting point of development looks like this:",[62,182,185],{"className":183,"code":184,"language":67},[65],"An idea occurs\n    ↓\nSay it out loud (Voice)\n    ↓\nAI understands and structures it\n    ↓\nAutomatically mounted onto \u002F updates the project tree\n",[69,186,184],{"__ignoreMap":71},[13,188,189,190,193,194,197,198,201],{},"You say: \"Sharing should support QR codes, and the codes need a configurable expiry.\" The AI understands, creates a ",[69,191,192],{},"QR code"," child node under the ",[69,195,196],{},"Sharing"," node of the project tree, and records the \"supports expiry\" constraint in its properties — along with ",[16,199,200],{},"the context in which you said it",". Three months later nobody has to excavate chat history; the full lineage of the decision lives on the tree.",[54,203,205],{"id":204},"_4-a-tree-shaped-knowledge-base-the-dynamic-project-tree","4. A tree-shaped knowledge base: the Dynamic Project Tree",[13,207,208],{},"Why a tree? Because the human mental model of a project naturally is one:",[62,210,213],{"className":211,"code":212,"language":67},[65],"Project\n├── Users\n│     ├── Login\n│     ├── Permissions\n│     └── Profile\n├── Sharing\n│     ├── QR code\n│     ├── Links\n│     └── Access control\n├── AI capabilities\n│     ├── Poster generation\n│     ├── OCR\n│     └── Copywriting\n└── Data\n      ├── Database\n      ├── API\n      └── Cache\n",[69,214,212],{"__ignoreMap":71},[13,216,217,218,133],{},"Ideas keep sprouting new nodes, rearranging relationships, pruning dead branches. The truth of a project should not live in a Word document that goes stale in a week — it should be this ",[16,219,220],{},"living tree",[13,222,223,224,227],{},"The essential difference from a traditional requirements document: each node on the tree is not a paragraph of prose, but a ",[16,225,226],{},"structured data object",":",[62,229,233],{"className":230,"code":231,"language":232,"meta":71,"style":71},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","node: Sharing\u002FQR code\ndescription: Generate expiring QR codes for shared content\nstatus: simulated        # idea → defined → simulated → implemented → verified\nowner: harry\ndepends_on: [Users\u002FPermissions, Data\u002FAPI]\ninterface:\n  input:  { content_id: string, ttl_hours: int }\n  output: { qr_url: string, expires_at: datetime }\nconstraints:\n  - Maximum validity is 72 hours\n  - Accessing after expiry returns 410\nhistory:\n  - 2026-07-12: Created via voice input, \"sharing should support QR codes\"\n  - 2026-07-14: Expiry constraint added (decided after discussion)\n","yaml",[69,234,235,251,262,277,288,311,320,352,382,390,399,407,415,429],{"__ignoreMap":71},[236,237,240,244,247],"span",{"class":238,"line":239},"line",1,[236,241,243],{"class":242},"swJcz","node",[236,245,227],{"class":246},"sMK4o",[236,248,250],{"class":249},"sfazB"," Sharing\u002FQR code\n",[236,252,254,257,259],{"class":238,"line":253},2,[236,255,256],{"class":242},"description",[236,258,227],{"class":246},[236,260,261],{"class":249}," Generate expiring QR codes for shared content\n",[236,263,265,268,270,273],{"class":238,"line":264},3,[236,266,267],{"class":242},"status",[236,269,227],{"class":246},[236,271,272],{"class":249}," simulated",[236,274,276],{"class":275},"sHwdD","        # idea → defined → simulated → implemented → verified\n",[236,278,280,283,285],{"class":238,"line":279},4,[236,281,282],{"class":242},"owner",[236,284,227],{"class":246},[236,286,287],{"class":249}," harry\n",[236,289,291,294,296,299,302,305,308],{"class":238,"line":290},5,[236,292,293],{"class":242},"depends_on",[236,295,227],{"class":246},[236,297,298],{"class":246}," [",[236,300,301],{"class":249},"Users\u002FPermissions",[236,303,304],{"class":246},",",[236,306,307],{"class":249}," Data\u002FAPI",[236,309,310],{"class":246},"]\n",[236,312,314,317],{"class":238,"line":313},6,[236,315,316],{"class":242},"interface",[236,318,319],{"class":246},":\n",[236,321,323,326,328,331,334,336,339,341,344,346,349],{"class":238,"line":322},7,[236,324,325],{"class":242},"  input",[236,327,227],{"class":246},[236,329,330],{"class":246},"  {",[236,332,333],{"class":242}," content_id",[236,335,227],{"class":246},[236,337,338],{"class":249}," string",[236,340,304],{"class":246},[236,342,343],{"class":242}," ttl_hours",[236,345,227],{"class":246},[236,347,348],{"class":249}," int",[236,350,351],{"class":246}," }\n",[236,353,355,358,360,363,366,368,370,372,375,377,380],{"class":238,"line":354},8,[236,356,357],{"class":242},"  output",[236,359,227],{"class":246},[236,361,362],{"class":246}," {",[236,364,365],{"class":242}," qr_url",[236,367,227],{"class":246},[236,369,338],{"class":249},[236,371,304],{"class":246},[236,373,374],{"class":242}," expires_at",[236,376,227],{"class":246},[236,378,379],{"class":249}," datetime",[236,381,351],{"class":246},[236,383,385,388],{"class":238,"line":384},9,[236,386,387],{"class":242},"constraints",[236,389,319],{"class":246},[236,391,393,396],{"class":238,"line":392},10,[236,394,395],{"class":246},"  -",[236,397,398],{"class":249}," Maximum validity is 72 hours\n",[236,400,402,404],{"class":238,"line":401},11,[236,403,395],{"class":246},[236,405,406],{"class":249}," Accessing after expiry returns 410\n",[236,408,410,413],{"class":238,"line":409},12,[236,411,412],{"class":242},"history",[236,414,319],{"class":246},[236,416,418,420,424,426],{"class":238,"line":417},13,[236,419,395],{"class":246},[236,421,423],{"class":422},"sTEyZ"," 2026-07-12",[236,425,227],{"class":246},[236,427,428],{"class":249}," Created via voice input, \"sharing should support QR codes\"\n",[236,430,432,434,437,439],{"class":238,"line":431},14,[236,433,395],{"class":246},[236,435,436],{"class":422}," 2026-07-14",[236,438,227],{"class":246},[236,440,441],{"class":249}," Expiry constraint added (decided after discussion)\n",[13,443,444,445],{},"A node carries its description, status, owner, dependencies, interface contract, constraints, and evolution history. The whole tree is a continuously growing knowledge graph of the project — it is the requirements base, the architecture diagram, the interface documentation, and the decision log. ",[16,446,447],{},"It is the project's single source of truth.",[54,449,451],{"id":450},"_5-the-key-step-every-node-becomes-a-virtual-function-first","5. The key step: every node becomes a \"virtual function\" first",[13,453,454],{},"This is the core of the methodology — and the sharpest break from \"using AI to write requirements docs.\"",[13,456,457,458,227],{},"A feature node on the tree must not stop at \"a paragraph of description.\" Once a node's definition is reasonably clear, the AI immediately promotes it into a ",[16,459,460],{},"Virtual Function",[83,462,463,469,475],{},[86,464,465,468],{},[16,466,467],{},"It has a real interface",": the input\u002Foutput schema is strictly defined — the very contract the future real implementation must honor",[86,470,471,474],{},[16,472,473],{},"It has a virtual implementation",": no real logic inside; the AI generates a \"behaviorally believable\" mock from the node's description — legal input returns semantically plausible simulated output, illegal input errors according to the constraints",[86,476,477,480],{},[16,478,479],{},"It can be called",": it is a genuinely runnable function\u002Fservice stub, not a box in a document",[13,482,483,484,486,487,490,491,133],{},"Take the ",[69,485,192],{}," node's virtual function: feed it ",[69,488,489],{},"{content_id: \"abc\", ttl_hours: 100}"," and it returns the error \"maximum validity is 72 hours\" — because the constraint is written on the node. Feed it legal parameters and it returns a structurally correct simulated ",[69,492,493],{},"qr_url",[13,495,496],{},[16,497,498],{},"Not one line of real business code exists, yet the feature already \"exists.\"",[13,500,501,502,505,506,509],{},"Engineers will find this familiar — it smells like a mock, a stub, the provider stub in contract testing. The difference: a traditional mock is an ",[16,503,504],{},"afterthought",", hand-maintained and disposable, written while testing code that already exists. Here, virtual functions are ",[16,507,508],{},"first-class citizens that precede the code"," — auto-generated by the AI from node definitions, auto-updated when definitions change, and — as the next section shows — wired together into a complete system.",[54,511,513],{"id":512},"_6-along-the-tree-wire-virtual-functions-into-a-virtual-system","6. Along the tree, wire virtual functions into a virtual system",[13,515,516,517],{},"A single virtual function is just a callable stub. The real power is this: ",[16,518,519],{},"the dependency edges on the tree are the system's wiring diagram.",[13,521,522,523,525],{},"Every node declares its ",[69,524,293],{}," and its interface contract, so the AI can walk those edges and assemble all the virtual functions:",[62,527,530],{"className":528,"code":529,"language":67},[65],"[Voice\u002FText] ──> Dynamic project tree ──> Virtual functions ──wired──> Virtual system\n                                                   │\n   Users\u002FLogin (virtual) ──> Users\u002FPermissions (virtual) ──> Sharing\u002FAccess control (virtual)\n                                                   │\n   Sharing\u002FQR code (virtual) \u003C── Data\u002FAPI (virtual) \u003C── Data\u002FDatabase (virtual)\n",[69,531,529],{"__ignoreMap":71},[13,533,534,535,227],{},"What you now hold is a ",[16,536,537],{},"system with zero real code that nevertheless runs as a whole",[83,539,540,543,546],{},[86,541,542],{},"Every module can be called, behaving exactly as its node defines",[86,544,545],{},"Modules genuinely call each other along the dependencies declared on the tree",[86,547,548,549,552],{},"The entire call topology ",[47,550,551],{},"is"," the architecture of the future real system",[13,554,555,556,559],{},"It is the software system's ",[16,557,558],{},"digital twin"," — except the twin is born before the original.",[13,561,562,563,566],{},"At this stage, a mass of problems that traditionally surface only at integration time appear instantly: two nodes' interfaces don't line up, dependencies form a cycle, a module assumes a data field its upstream never produces. ",[16,564,565],{},"The fix is not editing code — it is editing the tree",": adjust the node definitions, virtual functions regenerate automatically, the system rewires itself. The cost of change is effectively zero.",[54,568,570],{"id":569},"_7-data-flow-simulation-living-everyday-life-inside-the-virtual-system","7. Data-flow simulation: living everyday life inside the virtual system",[13,572,573,574,133],{},"With the virtual system assembled, verification begins: ",[16,575,576],{},"Data Flow Simulation",[13,578,579],{},"The AI generates simulation scenarios from the project tree — in essence a batch of end-to-end user journeys with bulk synthetic data:",[62,581,584],{"className":582,"code":583,"language":67},[65],"Scenario 1: New user registers → logs in → creates content → shares via QR code → someone scans and visits\nScenario 2: Visiting after the QR code expires → must return 410\nScenario 3: Unauthorized user tries to open an access-controlled share → must be blocked\nScenario 4: 1,000 concurrent share requests → watch data flow across API \u002F Cache \u002F DB\n",[69,585,583],{"__ignoreMap":71},[13,587,588,589,592],{},"Synthetic data is injected at the system's entry points and genuinely flows through the virtual system. On the graph you can ",[16,590,591],{},"see"," how data travels: from the login node into permissions, from the API node into cache; where flow stops dead, where data gets dropped, which path nobody ever reaches, which node every path converges on.",[13,594,595],{},"The questions simulation answers are exactly the ones the traditional process answers latest and at the highest price:",[83,597,598,604,610,616,622],{},[86,599,600,603],{},[16,601,602],{},"Does the business loop close?"," Can the user journey run start to finish?",[86,605,606,609],{},[16,607,608],{},"Is the data complete?"," Does upstream actually produce every field downstream consumes?",[86,611,612,615],{},[16,613,614],{},"Does the permission model leak?"," Is there any path that bypasses the permission node?",[86,617,618,621],{},[16,619,620],{},"Is the architecture sound?"," Are dependency directions clean? Any accidental coupling or bottleneck nodes?",[86,623,624,627],{},[16,625,626],{},"Were the failure paths considered?"," Expiry, failure, privilege escalation — does every error path have a destination?",[13,629,630,631,634],{},"Find a problem → edit the tree → virtual functions auto-update → re-simulate. This loop runs in ",[16,632,633],{},"minutes",", not \"one iteration.\" Product managers and engineers argue over the same living, flowing diagram instead of two mutually contradictory documents.",[13,636,637],{},[16,638,639],{},"When every simulation scenario passes, what you have verified is not code — it is the design itself.",[54,641,643],{"id":642},"_8-only-after-simulation-passes-does-real-programming-begin","8. Only after simulation passes does real programming begin",[13,645,646],{},"Only now does real programming start. And \"programming\" at this point means something entirely different:",[13,648,649,652],{},[16,650,651],{},"1. Contracts freeze."," Passing simulation means every node's interface contract, constraints, and dependencies have been verified. They freeze into the implementation baseline.",[13,654,655,658],{},[16,656,657],{},"2. Node-by-node \"materialization.\""," AI agents claim nodes and replace virtual implementations with real ones — real databases, real algorithms, real third-party calls. The external interface does not change by a single byte.",[13,660,661,664,665,668],{},[16,662,663],{},"3. Massive parallelism."," Because contracts are frozen, nodes are naturally decoupled: ten agents can materialize ten nodes simultaneously without blocking each other. One node not materialized yet? Fine — its virtual version still holds the line inside the system, which stays runnable at all times. This is hardware's ",[16,666,667],{},"mixed simulation",": real and virtual modules coexist, replaced incrementally.",[13,670,671,674,675,678],{},[16,672,673],{},"4. Simulation scenarios become the tests."," Every scenario and dataset accumulated during simulation converts automatically into integration and regression tests. A real implementation must produce the same results under the same scenarios before it may be marked ",[69,676,677],{},"verified",". Tests are not written after implementation — they exist before it, as the acceptance standard.",[13,680,681,684],{},[16,682,683],{},"5. Agents read the tree, not the code."," An agent picking up a task does not need to wade through hundreds of thousands of lines to guess design intent. It reads the project tree: current goal, node contract, dependencies, decision history, why it was designed this way. Code is merely this tree's projection at a moment in time.",[54,686,688],{"id":687},"_9-why-this-is-the-highest-efficiency-path","9. Why this is the highest-efficiency path",[13,690,691],{},"Put the two processes side by side:",[62,693,696],{"className":694,"code":695,"language":67},[65],"Traditional process:\n  Documents (static, quickly stale) → Coding → Design errors surface at integration → Massive rework\n  ─────────────── Errors surface at the most expensive stage ───────────────\n\nAI-native process:\n  Voice → Project tree → Virtual functions → Virtual system → Data-flow simulation → Contract freeze → Parallel materialization\n  ──────── Errors surface where change cost ≈ 0 ────────   ──── Code right the first time ────\n",[69,697,695],{"__ignoreMap":71},[13,699,700],{},"The efficiency advantage comes from four levers:",[13,702,703,706],{},[16,704,705],{},"Errors shift left to the cheapest point."," Decades of software engineering data point at one fact: the later a defect is found, the more it costs — from requirements stage to production, the difference can be a hundredfold. Simulation drags integration-level and architecture-level errors back to the stage where \"tweaking the tree\" fixes them.",[13,708,709,712],{},[16,710,711],{},"Coding turns from exploration into execution."," In traditional development, much coding is exploratory — you only learn whether the design works by writing it. After simulation passes, coding becomes execution of verified contracts, and that is precisely the kind of well-scoped, clear-boundary task AI agents are best at. Fast generation plus low rework is where the speed advantage actually cashes out.",[13,714,715,718],{},[16,716,717],{},"Parallelism is guaranteed by the architecture."," Contract-first makes nodes independently materializable; mixed running keeps the system demonstrable and runnable at every moment.",[13,720,721,724],{},[16,722,723],{},"The cost of understanding approaches zero."," Humans and all agents share one tree. A new member — human or agent — doesn't need \"three months to learn the codebase\"; reading the tree suffices. And requirements docs, interface docs, test docs are all generated from the tree — documentation is no longer where work begins, but what work outputs.",[54,726,728],{"id":727},"_10-the-complete-flow-and-the-vision","10. The complete flow, and the vision",[62,730,733],{"className":731,"code":732,"language":67},[65],"Idea (Voice)\n    ↓\nAI understanding & structuring\n    ↓\nDynamic project tree (tree-shaped knowledge base)\n    ↓\nNode → Virtual function (interface contract + AI-generated mock behavior)\n    ↓\nWire along the tree → Virtual system (the system's digital twin)\n    ↓\nData-flow simulation (all end-to-end scenarios pass)\n    ↓\nHuman sign-off, contract freeze\n    ↓\nAgents materialize in parallel (virtual → real, node by node)\n    ↓\nSimulation scenarios → automated tests → automated deployment\n",[69,734,732],{"__ignoreMap":71},[13,736,737],{},"In this paradigm, the developer's center of gravity shifts completely:",[83,739,740,746],{},[86,741,742,745],{},[16,743,744],{},"Humans",": generate ideas, make decisions, scrutinize the design inside the simulation, confirm direction",[86,747,748,751],{},[16,749,750],{},"AI",": captures thought, maintains the project tree, generates virtual functions, runs simulations, materializes code, verifies results",[13,753,754,755,758],{},"Software development stops being \"writing code\" and becomes ",[16,756,757],{},"cultivating a continuously growing digital model of the project",". The dynamic project tree is not merely a requirements tool — it is the project's digital organism. Code, docs, tests, interfaces are just the forms it automatically takes at different stages.",[13,760,761,762],{},"The chip industry spent thirty years proving one thing: ",[16,763,764],{},"making the system run in the virtual world first is the fastest road to the physical one.",[13,766,767],{},"It's time the software industry caught up on that lesson.",[10,769,770],{},[13,771,772],{},[16,773,40],{},[51,775],{},[13,777,778],{},[47,779,780],{},"This is a v0.2 draft — discussion welcome.",[51,782],{},[51,784],{},[786,787,789],"h1",{"id":788},"ai-native-软件工程先让系统虚拟地跑起来再写第一行代码","AI Native 软件工程：先让系统「虚拟地跑起来」，再写第一行代码",[10,791,792,814],{},[13,793,794,797,798,805,806,809,810,813],{},[16,795,796],{},"核心思想","：未来的软件开发，不以文档为中心，也不以代码为中心，而是以一棵",[16,799,800,801,804],{},"动态项目树（Dynamic Project Tree）",[16,802,803],{},"为中心。语音录入想法，长成树形知识库；每个节点先实现为","虚拟功能","；虚拟功能沿着树连接成一个",[16,807,808],{},"虚拟系统","；在虚拟系统上完成",[16,811,812],{},"数据流仿真","；仿真全部跑通之后，才开始真实的编程。",[13,815,816],{},[16,817,40],{},[10,819,820],{},[13,821,45,822],{},[47,823,824],{},"English version above — scroll up for the English translation.",[51,826],{},[54,828,830],{"id":829},"一ai-写代码越来越快软件却没有变快","一、AI 写代码越来越快，软件却没有变快",[13,832,833],{},"目前主流的 AI 编程工具——Copilot、Codex、Claude Code——本质上都嵌在一条几十年前设计的流水线里：",[62,835,838],{"className":836,"code":837,"language":67},[65],"需求 → 文档 → 原型 → 开发 → 测试 → 部署\n",[69,839,837],{"__ignoreMap":71},[13,841,842,843,846],{},"这条流水线诞生于",[16,844,845],{},"没有 AI 的时代","。AI 只是被插进了「开发」这一环，让写代码这一步变快了。",[13,848,849],{},"但任何做过真实项目的工程师都知道，写代码从来不是最慢的环节。真正消耗时间的是：",[83,851,852,855,858,861,864],{},[86,853,854],{},"需求在前期反复变化，代码写完才发现理解错了",[86,856,857],{},"架构调整牵一发动全身，改动成本随进度指数上升",[86,859,860],{},"文档永远落后于代码，没人相信文档",[86,862,863],{},"多个 AI Agent 各自读代码，对项目没有统一理解",[86,865,866],{},"关键的设计思路散落在会议、聊天记录和某个人的脑子里",[13,868,869,870],{},"把「写代码」这一步加速十倍，对上面这些问题的帮助是零。",[16,871,872],{},"错误依然要等到集成、联调、上线才暴露，而那时修复成本已经涨了几个数量级。",[13,874,875],{},"所以真正值得重构的不是 AI，而是整个软件工程流程本身。",[54,877,879],{"id":878},"二硬件行业早就给出了答案没有人不经仿真就流片","二、硬件行业早就给出了答案：没有人不经仿真就流片",[13,881,882,883],{},"芯片行业有一条铁律：",[16,884,885],{},"流片之前，必须仿真。",[13,887,888,889,892],{},"一次流片动辄数百万美元、数月周期，没有任何团队敢把没有仿真验证过的设计直接送去工厂。所以 EDA 工具链的核心从来不是「画电路更快」，而是：先在虚拟环境里把整个芯片",[16,890,891],{},"跑起来","——时序、功耗、信号完整性，全部在虚拟世界里验证通过，才进入物理世界。",[13,894,895,896,899],{},"再看软件行业：我们几乎是",[16,897,898],{},"裸奔着直接「流片」","。",[13,901,902,903,906],{},"需求文档写完（甚至没写完），就开始写真实代码，然后靠联调和测试在「物理世界」里撞出所有设计错误。我们容忍这种做法，只是因为软件的「重新流片」成本看起来低——改代码就好了嘛。但在 AI 时代，当 Agent 可以一夜之间生成数万行代码时，",[16,904,905],{},"返工的成本恰恰被放大了","：方向错了，生成得越快，浪费得越多。",[13,908,909],{},"AI 时代的软件工程，应该向硬件工程收敛：",[13,911,912],{},[16,913,914],{},"先构建虚拟系统 → 完成仿真验证 → 再进行真实实现。",[13,916,917],{},"这就是本文要展开的完整流程。",[54,919,921],{"id":920},"三语音是第一输入思维采集而不是打字提速","三、语音是第一输入：思维采集，而不是打字提速",[13,923,924,925,899],{},"流程的起点不是文档，而是",[16,926,927],{},"说话",[13,929,930],{},"人的想法不是线性的，灵感往往在一瞬间产生：走路时想到一个功能点、洗澡时意识到两个模块的依赖关系反了。如果此刻需要打开 Word、回忆模板、组织措辞，思路早就断了。",[13,932,933],{},"语音的价值不在于输入速度，而在于：",[13,935,936],{},[16,937,938],{},"它能以最低的摩擦，捕捉思考过程中的真实想法。",[10,940,941],{},[13,942,943],{},"Voice = Thought Capture（思维采集）",[13,945,946],{},"未来的开发起点是这样的：",[62,948,951],{"className":949,"code":950,"language":67},[65],"想到一个想法\n    ↓\n直接说出来（Voice）\n    ↓\nAI 理解、结构化\n    ↓\n自动挂载 \u002F 更新到项目树\n",[69,952,950],{"__ignoreMap":71},[13,954,955,956,959,960,963,964,967],{},"你说：「分享功能要支持二维码，而且要能设置有效期。」AI 听懂后，在项目树的 ",[69,957,958],{},"分享"," 节点下新建 ",[69,961,962],{},"二维码"," 子节点，并在其属性里记录「支持有效期」这条约束——同时记下",[16,965,966],{},"你是在什么上下文里说这句话的","。三个月后没有人需要考古聊天记录，决策的来龙去脉就长在树上。",[54,969,971],{"id":970},"四树形知识库动态项目树","四、树形知识库：动态项目树",[13,973,974],{},"为什么是树？因为人对项目的心智模型天然就是一棵树：",[62,976,979],{"className":977,"code":978,"language":67},[65],"Project\n├── 用户\n│     ├── 登录\n│     ├── 权限\n│     └── Profile\n├── 分享\n│     ├── 二维码\n│     ├── 链接\n│     └── 权限控制\n├── AI 能力\n│     ├── 海报生成\n│     ├── OCR\n│     └── 文案\n└── 数据\n      ├── 数据库\n      ├── API\n      └── Cache\n",[69,980,978],{"__ignoreMap":71},[13,982,983,984,899],{},"想法不断长出新节点、调整节点关系、剪掉废弃分支。项目的真相不该保存在一份很快过时的 Word 文档里，而应该是这棵",[16,985,986],{},"活着的树",[13,988,989,990,993],{},"和传统需求文档的本质区别在于：树上的每个节点不是一段自然语言描述，而是一个",[16,991,992],{},"结构化的数据对象","：",[62,995,997],{"className":230,"code":996,"language":232,"meta":71,"style":71},"node: 分享\u002F二维码\ndescription: 为分享内容生成带有效期的二维码\nstatus: simulated        # idea → defined → simulated → implemented → verified\nowner: harry\ndepends_on: [用户\u002F权限, 数据\u002FAPI]\ninterface:\n  input:  { content_id: string, ttl_hours: int }\n  output: { qr_url: string, expires_at: datetime }\nconstraints:\n  - 有效期最长 72 小时\n  - 过期后访问返回 410\nhistory:\n  - 2026-07-12: 由语音输入创建，「分享要支持二维码」\n  - 2026-07-14: 增加有效期约束（讨论后决策）\n",[69,998,999,1008,1017,1027,1035,1053,1059,1083,1107,1113,1120,1127,1133,1144],{"__ignoreMap":71},[236,1000,1001,1003,1005],{"class":238,"line":239},[236,1002,243],{"class":242},[236,1004,227],{"class":246},[236,1006,1007],{"class":249}," 分享\u002F二维码\n",[236,1009,1010,1012,1014],{"class":238,"line":253},[236,1011,256],{"class":242},[236,1013,227],{"class":246},[236,1015,1016],{"class":249}," 为分享内容生成带有效期的二维码\n",[236,1018,1019,1021,1023,1025],{"class":238,"line":264},[236,1020,267],{"class":242},[236,1022,227],{"class":246},[236,1024,272],{"class":249},[236,1026,276],{"class":275},[236,1028,1029,1031,1033],{"class":238,"line":279},[236,1030,282],{"class":242},[236,1032,227],{"class":246},[236,1034,287],{"class":249},[236,1036,1037,1039,1041,1043,1046,1048,1051],{"class":238,"line":290},[236,1038,293],{"class":242},[236,1040,227],{"class":246},[236,1042,298],{"class":246},[236,1044,1045],{"class":249},"用户\u002F权限",[236,1047,304],{"class":246},[236,1049,1050],{"class":249}," 数据\u002FAPI",[236,1052,310],{"class":246},[236,1054,1055,1057],{"class":238,"line":313},[236,1056,316],{"class":242},[236,1058,319],{"class":246},[236,1060,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081],{"class":238,"line":322},[236,1062,325],{"class":242},[236,1064,227],{"class":246},[236,1066,330],{"class":246},[236,1068,333],{"class":242},[236,1070,227],{"class":246},[236,1072,338],{"class":249},[236,1074,304],{"class":246},[236,1076,343],{"class":242},[236,1078,227],{"class":246},[236,1080,348],{"class":249},[236,1082,351],{"class":246},[236,1084,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105],{"class":238,"line":354},[236,1086,357],{"class":242},[236,1088,227],{"class":246},[236,1090,362],{"class":246},[236,1092,365],{"class":242},[236,1094,227],{"class":246},[236,1096,338],{"class":249},[236,1098,304],{"class":246},[236,1100,374],{"class":242},[236,1102,227],{"class":246},[236,1104,379],{"class":249},[236,1106,351],{"class":246},[236,1108,1109,1111],{"class":238,"line":384},[236,1110,387],{"class":242},[236,1112,319],{"class":246},[236,1114,1115,1117],{"class":238,"line":392},[236,1116,395],{"class":246},[236,1118,1119],{"class":249}," 有效期最长 72 小时\n",[236,1121,1122,1124],{"class":238,"line":401},[236,1123,395],{"class":246},[236,1125,1126],{"class":249}," 过期后访问返回 410\n",[236,1128,1129,1131],{"class":238,"line":409},[236,1130,412],{"class":242},[236,1132,319],{"class":246},[236,1134,1135,1137,1139,1141],{"class":238,"line":417},[236,1136,395],{"class":246},[236,1138,423],{"class":422},[236,1140,227],{"class":246},[236,1142,1143],{"class":249}," 由语音输入创建，「分享要支持二维码」\n",[236,1145,1146,1148,1150,1152],{"class":238,"line":431},[236,1147,395],{"class":246},[236,1149,436],{"class":422},[236,1151,227],{"class":246},[236,1153,1154],{"class":249}," 增加有效期约束（讨论后决策）\n",[13,1156,1157,1158],{},"节点带着描述、状态、负责人、依赖、接口契约、约束条件和演化历史。整棵树就是一张持续生长的项目知识图谱——它是需求库，是架构图，是接口文档，也是决策日志。",[16,1159,1160],{},"它是项目唯一的真相来源（Single Source of Truth）。",[54,1162,1164],{"id":1163},"五关键一步每个节点先做成虚拟功能","五、关键一步：每个节点先做成「虚拟功能」",[13,1166,1167],{},"这是整个方法论的核心，也是与「用 AI 写需求文档」最大的分野。",[13,1169,1170,1171,993],{},"树上的功能节点，不能只停留在「一段描述」。当一个节点的定义基本清晰后，AI 立刻把它升级为一个",[16,1172,1173],{},"虚拟功能（Virtual Function）",[83,1175,1176,1182,1188],{},[86,1177,1178,1181],{},[16,1179,1180],{},"有真实的接口","：输入输出 schema 是严格定义的，就是将来真实实现要遵守的契约",[86,1183,1184,1187],{},[16,1185,1186],{},"有虚拟的实现","：内部不写真实逻辑，由 AI 根据节点描述生成一个「行为逼真」的 mock——收到合法输入返回符合业务语义的模拟输出，收到非法输入按约束报错",[86,1189,1190,1193],{},[16,1191,1192],{},"可以被调用","：它是一个真正能跑的函数\u002F服务桩，而不是文档里的一个方框",[13,1195,1196,1197,1199,1200,1202,1203,899],{},"比如 ",[69,1198,962],{}," 节点的虚拟功能：输入 ",[69,1201,489],{},"，它会返回错误「有效期最长 72 小时」——因为约束就写在节点上。输入合法参数，它返回一个结构正确的模拟 ",[69,1204,493],{},[13,1206,1207],{},[16,1208,1209],{},"没有一行真实业务代码，但这个功能已经「存在」了。",[13,1211,1212,1213,1216,1217,1220],{},"工程师对这个东西并不陌生——它像 mock，像 stub，像契约测试里的 provider stub。区别在于：传统 mock 是为已有代码写测试时的",[16,1214,1215],{},"附属品","，是手工维护、随手即弃的；而这里的虚拟功能是",[16,1218,1219],{},"先于代码存在的一等公民","，由 AI 从节点定义自动生成、随节点定义自动更新，并且——下一节会讲到——它们要被连接起来，组成一个完整的系统。",[54,1222,1224],{"id":1223},"六沿着树把虚拟功能连接成一个虚拟系统","六、沿着树，把虚拟功能连接成一个虚拟系统",[13,1226,1227,1228],{},"单个虚拟功能只是一个可调用的桩。真正的威力在于：",[16,1229,1230],{},"树上的依赖关系，就是系统的接线图。",[13,1232,1233,1234,1236],{},"每个节点都声明了 ",[69,1235,293],{}," 和接口契约，AI 就可以沿着这些边，把所有虚拟功能组装起来：",[62,1238,1241],{"className":1239,"code":1240,"language":67},[65],"[语音\u002F文本] ──> 动态项目树 ──> 虚拟功能 ──连接──> 虚拟系统\n                                              │\n   用户\u002F登录（虚拟）──> 用户\u002F权限（虚拟）──> 分享\u002F权限控制（虚拟）\n                                              │\n   分享\u002F二维码（虚拟）\u003C── 数据\u002FAPI（虚拟）\u003C── 数据\u002F数据库（虚拟）\n",[69,1242,1240],{"__ignoreMap":71},[13,1244,1245,1246,993],{},"这时你得到的，是一个",[16,1247,1248],{},"没有任何真实代码、但整体可以运行的系统",[83,1250,1251,1254,1257],{},[86,1252,1253],{},"每个模块都能被调用，行为符合各自节点的定义",[86,1255,1256],{},"模块之间按照树上声明的依赖真实地互相调用",[86,1258,1259],{},"整个调用拓扑，就是未来真实系统的架构",[13,1261,1262,1263,1266],{},"它相当于整个软件系统的",[16,1264,1265],{},"数字孪生（Digital Twin）","——只不过孪生体先于本体出生。",[13,1268,1269,1270,1273],{},"在这一步，大量传统上要到联调阶段才暴露的问题会立刻现形：两个节点的接口对不上、依赖成环、某个模块假设的数据字段上游根本不产生。",[16,1271,1272],{},"修复方式不是改代码，而是改树","——调整节点定义，虚拟功能自动重新生成，系统重新连接。改动成本几乎为零。",[54,1275,1277],{"id":1276},"七数据流动仿真在虚拟系统上过日子","七、数据流动仿真：在虚拟系统上「过日子」",[13,1279,1280,1281,899],{},"虚拟系统搭好后，进入验证环节：",[16,1282,1283],{},"数据流动仿真（Data Flow Simulation）",[13,1285,1286],{},"AI 根据项目树生成仿真场景——本质上是一批端到端的用户旅程，配上批量模拟数据：",[62,1288,1291],{"className":1289,"code":1290,"language":67},[65],"场景 1: 新用户注册 → 登录 → 创建内容 → 生成二维码分享 → 他人扫码访问\n场景 2: 二维码过期后访问 → 应返回 410\n场景 3: 无权限用户尝试访问受控分享 → 应被拦截\n场景 4: 1000 个并发分享请求 → 观察数据在 API\u002FCache\u002FDB 间的流动\n",[69,1292,1290],{"__ignoreMap":71},[13,1294,1295,1296,1299],{},"模拟数据从系统入口注入，沿着虚拟系统真实地流动。你在图上",[16,1297,1298],{},"直观地看到","数据怎么走：从登录节点流向权限节点，从 API 节点流向缓存节点；哪里断流了，哪里数据被丢弃了，哪条路径根本没人走到，哪个节点成为所有路径的汇聚点。",[13,1301,1302],{},"仿真阶段要回答的问题，恰恰是传统流程里最晚、最贵才能回答的问题：",[83,1304,1305,1311,1317,1323,1329],{},[86,1306,1307,1310],{},[16,1308,1309],{},"业务闭环成立吗？"," 用户旅程能从头走到尾吗？",[86,1312,1313,1316],{},[16,1314,1315],{},"数据完备吗？"," 下游要用的字段，上游真的都产生了吗？",[86,1318,1319,1322],{},[16,1320,1321],{},"权限模型有漏洞吗？"," 有没有一条路径绕过了权限节点？",[86,1324,1325,1328],{},[16,1326,1327],{},"架构合理吗？"," 依赖方向是否清晰，有没有意外的耦合和瓶颈节点？",[86,1330,1331,1334],{},[16,1332,1333],{},"异常路径想过吗？"," 过期、失败、越权，每条错误路径都有归宿吗？",[13,1336,1337,1338,1341],{},"发现问题 → 修改树 → 虚拟功能自动更新 → 重新仿真。这个循环以",[16,1339,1340],{},"分钟","为单位，而不是以「一个迭代」为单位。产品经理和工程师看着同一张会流动的图讨论，而不是各自拿着两份互相矛盾的文档。",[13,1343,1344],{},[16,1345,1346],{},"当所有仿真场景全部通过时，你验证的不是代码，而是设计本身。",[54,1348,1350],{"id":1349},"八仿真通过才开始真实编程","八、仿真通过，才开始真实编程",[13,1352,1353],{},"到这一步，真实编程才开始。而此时的「编程」，已经和传统意义完全不同：",[13,1355,1356,1359],{},[16,1357,1358],{},"1. 契约冻结。"," 仿真通过意味着所有节点的接口契约、约束、依赖关系已被验证。它们被冻结为实现基线。",[13,1361,1362,1365],{},[16,1363,1364],{},"2. 逐节点「实化」。"," AI Agent 领取节点，把虚拟实现替换为真实实现——真实的数据库、真实的算法、真实的第三方调用。对外接口一个字节都不变。",[13,1367,1368,1371,1372,1375],{},[16,1369,1370],{},"3. 大规模并行。"," 因为契约已冻结，节点之间天然解耦：十个 Agent 可以同时实化十个节点，互不阻塞。一个节点还没实化完？没关系，它的虚拟版本仍在系统里顶着，整个系统始终可运行。这就是硬件仿真里的",[16,1373,1374],{},"混合仿真","——真实模块和虚拟模块共存，逐步替换。",[13,1377,1378,1381,1382,1384],{},[16,1379,1380],{},"4. 仿真场景直接变成测试。"," 仿真阶段积累的全部场景和数据，自动转化为集成测试和回归测试用例。真实实现必须在同样的场景下跑出同样的结果，才允许标记为 ",[69,1383,677],{},"。测试不是实现后补写的，而是实现前就已存在的验收标准。",[13,1386,1387,1390],{},[16,1388,1389],{},"5. Agent 读树，不读代码。"," Agent 接到任务时，不需要通读几十万行代码去猜设计意图。它读取项目树：当前目标、节点契约、依赖关系、历史决策、为什么这样设计。代码只是这棵树在某个时刻的投影。",[54,1392,1394],{"id":1393},"九为什么说这是效率最高的路径","九、为什么说这是效率最高的路径",[13,1396,1397],{},"把两条流程放在一起看：",[62,1399,1402],{"className":1400,"code":1401,"language":67},[65],"传统流程:\n  文档（静态、易过时）→ 编码 → 联调时发现设计错误 → 大规模返工\n  ────────────────── 错误暴露在最贵的阶段 ──────────────────\n\nAI Native 流程:\n  语音 → 项目树 → 虚拟功能 → 虚拟系统 → 数据流仿真 → 契约冻结 → 并行实化\n  ──────── 错误暴露在改动成本≈0的阶段 ────────   ──── 编码一次成型 ────\n",[69,1403,1401],{"__ignoreMap":71},[13,1405,1406],{},"效率优势来自四个杠杆：",[13,1408,1409,1412],{},[16,1410,1411],{},"错误左移到成本最低点。"," 软件工程数十年的数据都指向同一个事实：缺陷发现得越晚，修复成本越高，从需求阶段到上线阶段可能相差百倍。仿真把集成级、架构级的错误全部拉到「改一改树」就能修复的阶段消化掉。",[13,1414,1415,1418],{},[16,1416,1417],{},"编码从探索变成执行。"," 传统开发中大量编码是探索性的——写了才知道设计行不行。仿真通过后，编码变成对已验证契约的执行，AI Agent 最擅长的恰恰是这种目标明确、边界清晰的任务。生成快、返工少，速度优势才真正兑现。",[13,1420,1421,1424],{},[16,1422,1423],{},"并行度由架构保证。"," 契约先行让节点天然可并行实化，混合运行让系统在任何时刻都保持可运行、可演示。",[13,1426,1427,1430],{},[16,1428,1429],{},"理解成本趋近于零。"," 人和所有 Agent 共享同一棵树。新成员（无论人还是 Agent）不需要「熟悉代码库三个月」，读树即可。而需求文档、接口文档、测试文档，全部从树上自动生成——文档不再是工作的开始，而是工作的输出。",[54,1432,1434],{"id":1433},"十完整流程与愿景","十、完整流程与愿景",[62,1436,1439],{"className":1437,"code":1438,"language":67},[65],"想法（Voice）\n    ↓\nAI 理解、结构化\n    ↓\n动态项目树（树形知识库）\n    ↓\n节点 → 虚拟功能（接口契约 + AI 生成的 mock 行为）\n    ↓\n沿树连接 → 虚拟系统（系统的数字孪生）\n    ↓\n数据流动仿真（端到端场景全部跑通）\n    ↓\n人工确认、契约冻结\n    ↓\nAgent 并行实化（虚拟 → 真实，逐节点替换）\n    ↓\n仿真场景 → 自动化测试 → 自动部署\n",[69,1440,1438],{"__ignoreMap":71},[13,1442,1443],{},"在这个范式里，开发者的工作重心彻底改变：",[83,1445,1446,1452],{},[86,1447,1448,1451],{},[16,1449,1450],{},"人负责","：产生想法、做决策、在仿真中审视设计、确认方向",[86,1453,1454,1457],{},[16,1455,1456],{},"AI 负责","：采集思维、维护项目树、生成虚拟功能、执行仿真、实化代码、验证结果",[13,1459,1460,1461,1464],{},"软件开发不再是「写代码」，而是",[16,1462,1463],{},"培育一个持续成长的数字项目模型","。这棵动态项目树不只是需求管理工具，它是整个项目的数字生命体——代码、文档、测试、接口，都只是它在不同阶段自动生成的表现形式。",[13,1466,1467,1468],{},"芯片行业用了三十年时间证明：",[16,1469,1470],{},"先在虚拟世界里把系统跑通，是通往物理世界最快的路。",[13,1472,1473],{},"软件行业，是时候补上这一课了。",[10,1475,1476],{},[13,1477,1478],{},[16,1479,40],{},[51,1481],{},[13,1483,1484],{},[47,1485,1486],{},"本文为 v0.2 草稿，欢迎讨论。",[1488,1489,1490],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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":71,"searchDepth":253,"depth":253,"links":1492},[1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512],{"id":56,"depth":253,"text":57},{"id":112,"depth":253,"text":113},{"id":154,"depth":253,"text":155},{"id":204,"depth":253,"text":205},{"id":450,"depth":253,"text":451},{"id":512,"depth":253,"text":513},{"id":569,"depth":253,"text":570},{"id":642,"depth":253,"text":643},{"id":687,"depth":253,"text":688},{"id":727,"depth":253,"text":728},{"id":829,"depth":253,"text":830},{"id":878,"depth":253,"text":879},{"id":920,"depth":253,"text":921},{"id":970,"depth":253,"text":971},{"id":1163,"depth":253,"text":1164},{"id":1223,"depth":253,"text":1224},{"id":1276,"depth":253,"text":1277},{"id":1349,"depth":253,"text":1350},{"id":1393,"depth":253,"text":1394},{"id":1433,"depth":253,"text":1434},"tech","https:\u002F\u002Fimages.xtop.dev\u002Fblogs\u002F2026\u002F07\u002Fai-native-simulation-first.webp","2026-07-17","Why AI-era development should learn from chip design: grow a dynamic project tree from voice input, wire virtual functions into a virtual system, prove the design with data-flow simulation — and only then write real code.",false,"md",{},true,"\u002Fblogs\u002Fai-native-simulation-first-bilingual",null,{"title":5,"description":1516},"blogs\u002Fai-native-simulation-first-bilingual",[1526,1527,1528,1529,1530,1531,1532,1533],"ai-native","software-engineering","simulation","methodology","ai-agents","digital-twin","knowledge-tree","workflow","w0sCrJ3tqDdGYHdQd-NhSHEFmPpFF-7XyQjNFUz720o",{"prev":1536,"next":2292},{"id":1537,"title":1538,"body":1539,"category":1513,"cover":1547,"date":2279,"description":2280,"draft":1517,"extension":1518,"meta":2281,"navigation":1520,"path":2282,"readingTime":1522,"seo":2283,"stem":2284,"tags":2285,"__hash__":2291},"blogs\u002Fblogs\u002Fgoogle-kodak-moment-open-source-strategy.md","Back to the Android Playbook: How Google Can Break Free from Its Kodak Moment",{"type":7,"value":1540,"toc":2249},[1541,1548,1554,1556,1559,1562,1566,1569,1572,1579,1583,1586,1591,1652,1655,1659,1670,1673,1679,1683,1686,1690,1693,1699,1719,1725,1729,1733,1736,1743,1746,1772,1778,1782,1785,1790,1793,1813,1816,1820,1823,1826,1843,1846,1850,1853,1874,1878,1881,1888,1891,1893,1903,1905,1907,1911,1917,1919,1922,1925,1929,1932,1935,1942,1946,1949,1953,2007,2010,2014,2023,2026,2032,2036,2039,2043,2046,2052,2069,2075,2079,2083,2086,2093,2096,2122,2128,2132,2135,2140,2143,2163,2166,2170,2173,2176,2193,2196,2200,2203,2223,2226,2229,2236,2239,2241],[13,1542,1543],{},[1544,1545],"img",{"alt":1546,"src":1547},"cover","https:\u002F\u002Fimages.xtop.dev\u002Fblogs\u002F2026\u002F08\u002Fgoogle-kodak-moment-cover.webp",[10,1549,1550],{},[13,1551,45,1552],{},[47,1553,49],{},[51,1555],{},[13,1557,1558],{},"Today's Google (Alphabet) stands at a crossroads. It's caught between the anxiety of its search ad empire being eroded and the trap of chasing OpenAI's playbook in the generative AI wave. This posture — inventing the future but being held hostage by the existing cash cow — looks eerily similar to Kodak, which invented the digital camera and still went bankrupt.",[13,1560,1561],{},"But Google's biggest mistake isn't falling behind on technology. It's blindly treating OpenAI as the only paradigm for AI.",[54,1563,1565],{"id":1564},"kodaks-lesson-invented-the-future-died-in-the-past","Kodak's Lesson: Invented the Future, Died in the Past",[13,1567,1568],{},"In 1975, Kodak engineer Steve Sasson built the world's first digital camera. Management's first reaction wasn't excitement — it was fear: \"This thing will kill our film business.\"",[13,1570,1571],{},"Kodak's film empire was enormously profitable: high margins, a massive printing ecosystem, global distribution channels. Management instinctively shelved the digital camera to protect the cash cow, and was ultimately destroyed by the very future they had invented.",[13,1573,1574,1575,1578],{},"This is what Clayton Christensen defined as the ",[16,1576,1577],{},"\"Innovator's Dilemma\""," — successful companies trapped by their own success.",[54,1580,1582],{"id":1581},"googles-kodak-symptoms","Google's \"Kodak Symptoms\"",[13,1584,1585],{},"Today's Google shows strikingly similar symptoms across multiple dimensions.",[1587,1588,1590],"h3",{"id":1589},"_1-the-cash-cow-shackle","1. The Cash Cow Shackle",[1592,1593,1594,1609],"table",{},[1595,1596,1597],"thead",{},[1598,1599,1600,1603,1606],"tr",{},[1601,1602],"th",{},[1601,1604,1605],{},"Kodak",[1601,1607,1608],{},"Google",[1610,1611,1612,1626,1639],"tbody",{},[1598,1613,1614,1620,1623],{},[1615,1616,1617],"td",{},[16,1618,1619],{},"Core Revenue",[1615,1621,1622],{},"Film sales (razor-and-blade model)",[1615,1624,1625],{},"Search ads (70%+ of revenue)",[1598,1627,1628,1633,1636],{},[1615,1629,1630],{},[16,1631,1632],{},"Disruptive Threat",[1615,1634,1635],{},"Digital photography",[1615,1637,1638],{},"Generative AI direct answers",[1598,1640,1641,1646,1649],{},[1615,1642,1643],{},[16,1644,1645],{},"Dilemma",[1615,1647,1648],{},"Promote digital cameras = kill film",[1615,1650,1651],{},"Promote AI answers = cannibalize blue links",[13,1653,1654],{},"When users shift from \"scanning a list of links\" to \"getting direct answers,\" Google's most profitable sponsored link placements lose their conversion value. Same logic as digital photos shutting down print shops.",[1587,1656,1658],{"id":1657},"_2-invented-the-future-trapped-by-the-future","2. Invented the Future, Trapped by the Future",[13,1660,1661,1662,1665,1666,1669],{},"In 2017, Google published the landmark paper ",[47,1663,1664],{},"Attention Is All You Need",", introducing the ",[16,1667,1668],{},"Transformer"," architecture that directly ignited this round of AI revolution. Even core concepts like \"Hallucination\" were first coined by Google researchers.",[13,1671,1672],{},"Yet it was OpenAI that turned Transformers into a disruptive consumer product (ChatGPT).",[13,1674,1675,1676],{},"The root cause: Google feared AI hallucinations would damage its commercial reputation and worried AI direct answers would cannibalize search results, leading to indecisive, slow product execution. ",[16,1677,1678],{},"Exactly like Kodak inventing the digital camera but not daring to promote it.",[1587,1680,1682],{"id":1681},"_3-big-company-disease","3. Big Company Disease",[13,1684,1685],{},"Kodak had layers of middle management and bureaucratic processes; Google has been equally plagued by \"big company syndrome\" in recent years — countless overlapping internal projects (remember all those messaging apps that were built and killed?), with employees incentivized toward promotion-friendly micro-innovations rather than risky disruption of existing business lines.",[54,1687,1689],{"id":1688},"why-google-shouldnt-try-to-be-the-next-openai","Why Google Shouldn't Try to Be the Next OpenAI",[13,1691,1692],{},"OpenAI follows a classic \"unicorn model\" — high cost, closed source, monetized through premium subscriptions. If Google tries to compete head-on in this lane, it's fighting with its weakness against the competitor's strength, while also bearing the burden of its search ads being cannibalized.",[13,1694,1695,1696,227],{},"Looking back at Google's rise, its core moat was never proprietary closed technology — it was ",[16,1697,1698],{},"ecosystem dominance through openness",[83,1700,1701,1707,1713],{},[86,1702,1703,1706],{},[16,1704,1705],{},"Android",": defeated Windows Phone and Symbian through open source, monopolizing global mobile traffic via bundled services",[86,1708,1709,1712],{},[16,1710,1711],{},"Chromium",": open-sourced the browser engine, destroyed IE, and made Chrome the internet's default gateway",[86,1714,1715,1718],{},[16,1716,1717],{},"Kubernetes & TensorFlow",": open-sourced cloud-native standards and deep learning frameworks, establishing Google's authority in the developer community",[13,1720,1721,1724],{},[16,1722,1723],{},"Open source is the antibody in Google's bloodstream."," When you apply this logic to the AI era, the answer becomes clear.",[54,1726,1728],{"id":1727},"the-breakout-triangle-open-source-small-models-hardware-alliance","The Breakout Triangle: Open Source × Small Models × Hardware Alliance",[1587,1730,1732],{"id":1731},"i-open-source-the-symmetric-weapon-against-closed-walls","I. Open Source: The Symmetric Weapon Against Closed Walls",[13,1734,1735],{},"When every developer and enterprise in the world can access industrial-grade open-source models with zero barriers, OpenAI's expensive subscription and API model rapidly loses its justification.",[13,1737,1738,1739,1742],{},"More importantly — ",[16,1740,1741],{},"the endgame of software is ecosystem",". As long as millions of developers worldwide build fine-tunes and applications on Google's open-source framework, Google retains control of AI's technical standards.",[13,1744,1745],{},"But Google isn't doing this well enough. Take the Gemma series as an example — its open-source strategy exposes several critical flaws:",[83,1747,1748,1754,1760,1766],{},[86,1749,1750,1753],{},[16,1751,1752],{},"\"Wants to open source, but won't give the good stuff\"",": core technology and large-parameter models are all locked behind closed APIs; what's open-sourced is the B-team",[86,1755,1756,1759],{},[16,1757,1758],{},"Safety reins pulled too tight",": excessive alignment makes the model rigid and lifeless in creative tasks and open discussions",[86,1761,1762,1765],{},[16,1763,1764],{},"Code capability cliff",": same-size Gemma consistently gets outperformed by Qwen and even DeepSeek on code benchmarks",[86,1767,1768,1771],{},[16,1769,1770],{},"Starved community ecosystem",": Gemma-related derivative projects on Hugging Face lag far behind the Qwen and DeepSeek ecosystems in both quantity and quality",[13,1773,1774,1775],{},"By contrast, the teams truly practicing the spirit of open source are Chinese organizations. DeepSeek built open-source models rivaling GPT-4 at a fraction of the training cost, winning global developer respect through sheer technical transparency. Alibaba Cloud's Qwen series covers dialogue, code, and multimodal tasks, with Hugging Face community activity and derivative ecosystems already far surpassing Gemma. ",[16,1776,1777],{},"A search giant invented the Transformer, yet the fruits of the open-source ecosystem were picked by startups and latecomers — this is the reality that should alarm Google most.",[1587,1779,1781],{"id":1780},"ii-small-language-models-slm-the-volume-play-for-device-dominance","II. Small Language Models (SLM): The Volume Play for Device Dominance",[13,1783,1784],{},"OpenAI's paradigm led the industry toward a blind worship of \"bigger is better\" — trillion-parameter models are certainly smart, but their high latency, high cost, and extreme power consumption make mass adoption impossible.",[13,1786,1787],{},[16,1788,1789],{},"AI's endgame isn't cloud-based compute dominance — it's ubiquitous on-device intelligence.",[13,1791,1792],{},"What can actually reach billions of users daily are 3B to 8B parameter models that run directly on devices. This is precisely the battlefield where Google holds an overwhelming advantage:",[83,1794,1795,1801,1807],{},[86,1796,1797,1800],{},[16,1798,1799],{},"Controls the ultimate entry points",": Android and Chrome reach billions of users globally",[86,1802,1803,1806],{},[16,1804,1805],{},"Local, instant response",": embed competitive small open-source models natively into the OS — users need no internet, no API payments",[86,1808,1809,1812],{},[16,1810,1811],{},"Privacy and security",": data never leaves the device, naturally complying with privacy regulations",[13,1814,1815],{},"Win the devices with small models, and you win the broadest consumer market.",[1587,1817,1819],{"id":1818},"iii-ally-with-intel-and-amd-build-the-anti-nvidia-compute-alliance","III. Ally with Intel and AMD: Build the Anti-Nvidia Compute Alliance",[13,1821,1822],{},"In the cloud, Nvidia has built an extraordinarily stubborn compute monopoly with GPUs and CUDA; the OpenAI model turns the entire industry into Nvidia's \"tenants.\" Google fighting alone with TPU in the cloud is still playing by the opponent's rules.",[13,1824,1825],{},"The key to breaking through is shifting the perspective from cloud to edge, forming a deep hardware-software alliance with Intel and AMD:",[83,1827,1828,1833,1838],{},[86,1829,1830],{},[16,1831,1832],{},"Intel and AMD control the world's dominant CPU and next-gen NPU ecosystem",[86,1834,1835],{},[16,1836,1837],{},"Google deeply optimizes small open-source models for Intel NPU and AMD Ryzen AI\u002FROCm architectures",[86,1839,1840],{},[16,1841,1842],{},"Achieving zero-barrier local inference, bypassing expensive GPU requirements",[13,1844,1845],{},"When \"Google open-source model + Intel\u002FAMD chip\" becomes the factory default for AI PCs, billions of edge devices worldwide become the breeding ground for Google's ecosystem.",[54,1847,1849],{"id":1848},"why-google-hesitates","Why Google Hesitates",[13,1851,1852],{},"If open-source ecosystem is in Google's DNA, why is it moving so cautiously? Three reasons:",[1854,1855,1856,1862,1868],"ol",{},[86,1857,1858,1861],{},[16,1859,1860],{},"Financial sunk cost",": training top-tier hundred-billion-parameter models costs hundreds of millions of dollars. Under Wall Street's quarterly earnings pressure, management struggles to commit to giving away top models for free",[86,1863,1864,1867],{},[16,1865,1866],{},"Safety liability",": as a megacorp, if Google open-sources a top model that gets misused, the regulatory accountability and PR risk far exceed what a startup would face",[86,1869,1870,1873],{},[16,1871,1872],{},"Internal strategic tug-of-war",": Google still hasn't truly chosen between \"use closed models to defend the search ad base\" and \"go full open source to rebuild the cloud ecosystem\"",[54,1875,1877],{"id":1876},"conclusion","Conclusion",[13,1879,1880],{},"OpenAI is an episode in AI's early evolution, not the industry's final paradigm.",[13,1882,1883,1884,1887],{},"From \"blindly chasing closed-source APIs\" to ",[16,1885,1886],{},"\"open-source top models + small models to capture devices + hardware alliances to empower the ecosystem + GCP to harvest the compute infrastructure\""," — this isn't just a return to Google's historical DNA; it's the only path away from its Kodak Moment.",[13,1889,1890],{},"Drop the ego, break the old paradigm, re-embrace developers and hardware allies — and the Google that once rewrote the rules can do it again, on its own terms.",[51,1892],{},[13,1894,1895],{},[47,1896,1897,1898,133],{},"This article is a personal observation on Google's strategic dilemma. If you're interested in full-stack development or AI application deployment, feel free to ",[1899,1900,1902],"a",{"href":1901},"\u002Fservices#contact","get in touch",[51,1904],{},[51,1906],{},[786,1908,1910],{"id":1909},"重回-android-时代google-摆脱柯达魔咒的破局之道","重回 Android 时代：Google 摆脱「柯达魔咒」的破局之道",[10,1912,1913],{},[13,1914,45,1915],{},[47,1916,824],{},[51,1918],{},[13,1920,1921],{},"今天的 Google（Alphabet）正伫立在命运的十字路口。它深陷于\"传统搜索广告被侵蚀\"的焦虑，又在生成式 AI 的浪潮中陷入了追赶 OpenAI 的误区。这种\"发明了未来却被现有现金牛困住\"的姿态，像极了当年发明数码相机却最终倒下的柯达。",[13,1923,1924],{},"但 Google 最大的失误，并不是技术落后，而是盲目地将 OpenAI 视为 AI 的唯一范式。",[54,1926,1928],{"id":1927},"柯达的教训发明了未来却死在了过去","柯达的教训：发明了未来，却死在了过去",[13,1930,1931],{},"1975 年，柯达工程师 Steve Sasson 发明了世界上第一台数码相机。管理层看到这项技术后的第一反应不是兴奋，而是恐惧——\"这玩意会杀死我们的胶卷业务\"。",[13,1933,1934],{},"柯达的胶卷帝国太赚钱了：高毛利、庞大的冲印生态系统、全球化的渠道网络。管理层出于保护核心现金牛的本能，将数码相机束之高阁，最终被自己发明的未来彻底颠覆。",[13,1936,1937,1938,1941],{},"这就是克莱顿·克里斯坦森所定义的 ",[16,1939,1940],{},"\"创新者的窘境\"（Innovator's Dilemma）","——成功企业被自身的成功所困。",[54,1943,1945],{"id":1944},"google-的柯达式隐患","Google 的\"柯达式隐患\"",[13,1947,1948],{},"对比今天的 Google，它在多个维度上表现出了极其相似的症状。",[1587,1950,1952],{"id":1951},"_1-现金牛的枷锁","1. 现金牛的枷锁",[1592,1954,1955,1966],{},[1595,1956,1957],{},[1598,1958,1959,1961,1964],{},[1601,1960],{},[1601,1962,1963],{},"柯达",[1601,1965,1608],{},[1610,1967,1968,1981,1994],{},[1598,1969,1970,1975,1978],{},[1615,1971,1972],{},[16,1973,1974],{},"核心营收",[1615,1976,1977],{},"胶卷销售（剃刀模式）",[1615,1979,1980],{},"搜索广告（超过 70% 营收）",[1598,1982,1983,1988,1991],{},[1615,1984,1985],{},[16,1986,1987],{},"颠覆性威胁",[1615,1989,1990],{},"数码摄影",[1615,1992,1993],{},"生成式 AI 直答",[1598,1995,1996,2001,2004],{},[1615,1997,1998],{},[16,1999,2000],{},"两难困境",[1615,2002,2003],{},"推广数码相机 = 自杀胶卷业务",[1615,2005,2006],{},"推广 AI 直答 = 蚕食蓝色链接广告位",[13,2008,2009],{},"当用户获取信息的方式从\"在链接列表中筛选\"变成\"直接获取答案\"，Google 最赚钱的赞助商链接广告位就失去了转化价值。这和当年数码照片让冲印店关门是同一个逻辑。",[1587,2011,2013],{"id":2012},"_2-发明了未来却被未来困住","2. 发明了未来，却被未来困住",[13,2015,2016,2017,2019,2020,2022],{},"2017 年，Google 发表了划时代的论文 ",[47,2018,1664],{},"，提出了 ",[16,2021,1668],{}," 架构，直接开创了这一轮 AI 革命的技术基础。甚至\"Hallucination（幻觉）\"等核心概念也是 Google 研究员率先提出的。",[13,2024,2025],{},"然而，将 Transformer 转化为颠覆性消费级产品（ChatGPT）的却是 OpenAI。",[13,2027,2028,2029],{},"根源在于：Google 既担心 AI 幻觉破坏商业声誉，又担心 AI 直答取代搜索结果，最终在产品化上举棋不定、步子太慢。",[16,2030,2031],{},"和柯达发明数码相机却不敢推广，如出一辙。",[1587,2033,2035],{"id":2034},"_3-大企业病","3. 大企业病",[13,2037,2038],{},"柯达有庞大的中层管理体系和官僚流程；Google 近年来同样饱受\"大公司病\"诟病——内部项目众多但缺乏统筹（回忆一下那些被反复重做又被砍掉的通讯应用），员工倾向于做能\"升职加薪\"的微创新，而不是承担风险去颠覆现有业务。",[54,2040,2042],{"id":2041},"为什么-google-不应该成为第二个-openai","为什么 Google 不应该成为第二个 OpenAI",[13,2044,2045],{},"OpenAI 走的是典型的\"独角兽模式\"——高造价、高闭源、靠卖高价订阅变现。如果 Google 跟着在这条赛道上死磕，不仅要背负搜索广告被侵蚀的包袱，更是用自己的短板去拼别人的长板。",[13,2047,2048,2049,993],{},"回顾 Google 的崛起史，它的核心壁垒从不是封闭的独占技术，而是 ",[16,2050,2051],{},"开放生态的统治力",[83,2053,2054,2059,2064],{},[86,2055,2056,2058],{},[16,2057,1705],{},"：通过开源击败了 Windows Phone 和 Symbian，靠内置服务垄断了全球移动端流量",[86,2060,2061,2063],{},[16,2062,1711],{},"：将浏览器底层开源，彻底瓦解了 IE，让 Chrome 成为互联网的绝对入口",[86,2065,2066,2068],{},[16,2067,1717],{},"：将云原生标准和深度学习框架开源，奠定了 Google 在技术社区的宗师地位",[13,2070,2071,2074],{},[16,2072,2073],{},"开源才是 Google 血液里自带的抗体。"," 当你把这个逻辑套用到 AI 时代，答案就清晰了。",[54,2076,2078],{"id":2077},"破局三角开源-精简模型-硬件联盟","破局三角：开源 × 精简模型 × 硬件联盟",[1587,2080,2082],{"id":2081},"一开源瓦解闭源高墙的对称武器","一、开源：瓦解闭源高墙的对称武器",[13,2084,2085],{},"当全天下的开发者与企业都能无门槛获得工业级的开源模型时，OpenAI 式的高昂订阅与 API 模式将迅速失去合理性。",[13,2087,2088,2089,2092],{},"更重要的是——",[16,2090,2091],{},"软件的终局是生态","。只要全球上百万开发者基于 Google 的开源框架做微调和应用开发，未来的 AI 技术标准就依然掌握在 Google 手中。",[13,2094,2095],{},"但 Google 目前做得还不够好。以 Gemma 系列为例，它的开源策略暴露了几个致命问题：",[83,2097,2098,2104,2110,2116],{},[86,2099,2100,2103],{},[16,2101,2102],{},"\"既想开源，又舍不得给好料\"","：核心的优秀技术和超大参数量全都锁在闭源 API 后面，开源的只是\"次等马\"",[86,2105,2106,2109],{},[16,2107,2108],{},"安全缰绳套得太紧","：过度对齐（Alignment）导致模型在创意任务和开放性讨论中生硬死板",[86,2111,2112,2115],{},[16,2113,2114],{},"代码能力断崖式落后","：同尺寸的 Gemma 在代码基准测试中往往被 Qwen 甚至 DeepSeek 完虐",[86,2117,2118,2121],{},[16,2119,2120],{},"社区生态匮乏","：Hugging Face 上围绕 Gemma 的二次开发项目数量和质量远落后于 Qwen 和 DeepSeek 生态",[13,2123,2124,2125],{},"相比之下，真正在践行开源精神的是中国团队。DeepSeek 以极低的训练成本打造出性能比肩 GPT-4 的开源模型，直接用技术透明度赢得了全球开发者的尊重；阿里云的 Qwen 系列则从对话、代码到多模态全面开花，在 Hugging Face 上的社区活跃度和二次开发生态已经远超 Gemma。",[16,2126,2127],{},"一个搜索巨头发明了 Transformer，开源生态的果实却被创业公司和后来者摘走——这才是 Google 最应该警醒的现实。",[1587,2129,2131],{"id":2130},"二精简模型slm占据终端的走量杀手锏","二、精简模型（SLM）：占据终端的走量杀手锏",[13,2133,2134],{},"OpenAI 的范式将行业引向了\"越大越好\"的盲目崇拜——千亿参数的模型固然聪明，但高延迟、高成本、极度烧电，根本无法支撑大规模普及应用。",[13,2136,2137],{},[16,2138,2139],{},"AI 的终局绝非云端的算力霸权，而是无处不在的端侧智能。",[13,2141,2142],{},"真正能走进亿万用户日常的，是 3B 到 8B 参数量级、能够直接跑在设备上的精简模型。这恰恰是 Google 拥有压倒性优势的绝对主场：",[83,2144,2145,2151,2157],{},[86,2146,2147,2150],{},[16,2148,2149],{},"掌控绝对入口","：Android 与 Chrome 触达全球数十亿用户",[86,2152,2153,2156],{},[16,2154,2155],{},"本地化秒级响应","：将精简开源模型原生植入系统，用户无需联网、无需支付 API 费用",[86,2158,2159,2162],{},[16,2160,2161],{},"隐私与安全","：数据不出设备，天然满足隐私法规",[13,2164,2165],{},"精简模型赢得了终端，就赢得了最广阔的消费级市场。",[1587,2167,2169],{"id":2168},"三联合-intel-与-amd组建反-nvidia-算力联盟","三、联合 Intel 与 AMD：组建反 Nvidia 算力联盟",[13,2171,2172],{},"在云端，Nvidia 靠 GPU 和 CUDA 打造了极其顽固的算力垄断；OpenAI 模式让全行业都沦为 Nvidia 的\"租客\"。Google 仅靠 TPU 孤军奋战，依然是在对方的规则里打仗。",[13,2174,2175],{},"破局的关键在于把视角从云端投向端侧，与 Intel 和 AMD 结成深度的软硬一体战略联盟：",[83,2177,2178,2183,2188],{},[86,2179,2180],{},[16,2181,2182],{},"Intel 与 AMD 控制着全球主流的 CPU 和新一代 NPU 生态",[86,2184,2185],{},[16,2186,2187],{},"Google 将精简开源模型针对 Intel NPU 与 AMD Ryzen AI\u002FROCm 做底层深度优化",[86,2189,2190],{},[16,2191,2192],{},"实现零门槛本地运行，绕过高昂的 GPU 门槛",[13,2194,2195],{},"当\"Google 开源模型 + Intel\u002FAMD 芯片\"成为 AI PC 的出厂标配，全球数以亿计的终端设备就会变成 Google 生态的繁衍土壤。",[54,2197,2199],{"id":2198},"为什么-google-现在如此犹豫","为什么 Google 现在如此犹豫",[13,2201,2202],{},"既然开源生态是 Google 的基因，为什么它走得束手束脚？三个原因：",[1854,2204,2205,2211,2217],{},[86,2206,2207,2210],{},[16,2208,2209],{},"财务上的沉没成本","：训练千亿级顶尖模型需要数亿美元。在华尔街季报的压力下，管理层极难下定决心把最顶级的模型免费开源",[86,2212,2213,2216],{},[16,2214,2215],{},"安全的责任枷锁","：作为巨头，Google 一旦开源了被滥用的顶级模型，面临的监管问责和舆情风险远高于创业公司",[86,2218,2219,2222],{},[16,2220,2221],{},"内部的战略撕扯","：在\"用闭源模型守护搜索广告基本盘\"和\"彻底开源重构云生态\"之间，Google 至今没有做出真正的抉择",[54,2224,2225],{"id":2225},"结语",[13,2227,2228],{},"OpenAI 只是 AI 演进初期的一段插曲，绝非产业发展的终局范式。",[13,2230,2231,2232,2235],{},"从\"盲目追赶闭源 API\"转向 ",[16,2233,2234],{},"\"开源顶级模型 + 精简模型占领终端 + 联合硬件巨头赋能生态 + GCP 收割算力底座\"","——这不仅是 Google 历史基因的复归，更是其告别\"柯达时刻\"的唯一通途。",[13,2237,2238],{},"放下面子，打破传统范式，重新拥抱开发者与硬件盟友——那个曾经无往不胜的 Google，就能再次按自己的节奏改写时代规则。",[51,2240],{},[13,2242,2243],{},[47,2244,2245,2246,899],{},"这篇文章是对 Google 战略困境的个人观察与思考。如果你对全栈开发或 AI 应用落地有兴趣，欢迎",[1899,2247,2248],{"href":1901},"聊聊",{"title":71,"searchDepth":253,"depth":253,"links":2250},[2251,2252,2257,2258,2263,2264,2265,2266,2271,2272,2277,2278],{"id":1564,"depth":253,"text":1565},{"id":1581,"depth":253,"text":1582,"children":2253},[2254,2255,2256],{"id":1589,"depth":264,"text":1590},{"id":1657,"depth":264,"text":1658},{"id":1681,"depth":264,"text":1682},{"id":1688,"depth":253,"text":1689},{"id":1727,"depth":253,"text":1728,"children":2259},[2260,2261,2262],{"id":1731,"depth":264,"text":1732},{"id":1780,"depth":264,"text":1781},{"id":1818,"depth":264,"text":1819},{"id":1848,"depth":253,"text":1849},{"id":1876,"depth":253,"text":1877},{"id":1927,"depth":253,"text":1928},{"id":1944,"depth":253,"text":1945,"children":2267},[2268,2269,2270],{"id":1951,"depth":264,"text":1952},{"id":2012,"depth":264,"text":2013},{"id":2034,"depth":264,"text":2035},{"id":2041,"depth":253,"text":2042},{"id":2077,"depth":253,"text":2078,"children":2273},[2274,2275,2276],{"id":2081,"depth":264,"text":2082},{"id":2130,"depth":264,"text":2131},{"id":2168,"depth":264,"text":2169},{"id":2198,"depth":253,"text":2199},{"id":2225,"depth":253,"text":2225},"2026-08-10","A deep comparison between Google's current AI dilemma and Kodak's historic downfall — both invented the future but were trapped by their cash cows. The way out isn't becoming the next OpenAI, but returning to Google's open-source DNA.",{},"\u002Fblogs\u002Fgoogle-kodak-moment-open-source-strategy",{"title":1538,"description":2280},"blogs\u002Fgoogle-kodak-moment-open-source-strategy",[2286,2287,2288,2289,2290],"google","ai","open-source","strategy","opinion","niIUlN3IpS9KsLbaPvKHmY6Xl-IUyHtanX5Nbgw6nHg",{"id":2293,"title":2294,"body":2295,"category":1513,"cover":3142,"date":3143,"description":3144,"draft":1517,"extension":1518,"meta":3145,"navigation":1520,"path":3146,"readingTime":1522,"seo":3147,"stem":3148,"tags":3149,"__hash__":3159},"blogs\u002Fblogs\u002Ftravel-dm-platform-retrospective-bilingual.md","A Ten-Year Retrospective: A Nationwide B2B Digital Publishing Platform for Travel DM Magazines",{"type":7,"value":2296,"toc":3116},[2297,2302,2304,2308,2311,2318,2322,2333,2336,2350,2357,2361,2364,2409,2415,2418,2422,2433,2436,2440,2443,2454,2461,2468,2472,2478,2481,2488,2491,2502,2505,2509,2516,2519,2539,2550,2554,2561,2568,2571,2591,2594,2598,2601,2607,2610,2636,2639,2643,2646,2672,2678,2682,2689,2692,2703,2706,2708,2710,2714,2719,2726,2728,2731,2734,2737,2740,2751,2754,2768,2775,2779,2782,2827,2833,2836,2840,2851,2854,2858,2861,2872,2879,2885,2889,2895,2898,2905,2908,2919,2922,2926,2933,2936,2956,2966,2970,2976,2983,2986,3006,3009,3012,3015,3021,3024,3050,3053,3056,3059,3085,3091,3094,3101,3104,3113],[10,2298,2299],{},[13,2300,2301],{},"Project timeline: 2006 — 2019\nStack: .NET (ASPX) \u002F SQL Server \u002F jQuery \u002F in-house automated sync tooling \u002F distributed file storage",[51,2303],{},[54,2305,2307],{"id":2306},"preface","Preface",[13,2309,2310],{},"This is a project I worked on for ten years. It was shut down in 2019, partly because of the pandemic and partly because the industry had moved on. Looking back now, it carries every hallmark of that pre-mobile-internet era: ASPX, jQuery, hand-written SQL, file server clusters — none of which would show up on the first page of anyone's tech-selection deck today. But it ran steadily for a decade, and it actually solved a problem that no one else had solved well at the time.",[13,2312,2313,2314,2317],{},"I want to write down the engineering decisions behind it while I still remember the details — not to argue that any of it was sophisticated, but to remember ",[47,2315,2316],{},"why"," we did things the way we did.",[54,2319,2321],{"id":2320},"what-the-project-actually-did","What the project actually did",[13,2323,2324,2325,2328,2329,2332],{},"Before mobile internet really took hold — especially between 2009 and 2013 — ",[16,2326,2327],{},"pricing and itinerary information"," flowed between travel agencies across China primarily through a very traditional channel: ",[16,2330,2331],{},"DM magazines"," (direct-mail advertising magazines). Each province's branch would publish a weekly issue, laying out their products, prices, and departure dates, printing them, and shipping them to peer agencies. Downstream agencies flipped through the magazines, made phone calls, and booked products.",[13,2334,2335],{},"Two pain points were painfully obvious:",[83,2337,2338,2344],{},[86,2339,2340,2343],{},[16,2341,2342],{},"Poor timeliness."," Once a magazine was printed and shipped nationwide, several days had already passed — and prices may have already shifted.",[86,2345,2346,2349],{},[16,2347,2348],{},"Information silos."," A single agency would only subscribe to a handful of magazines. They couldn't see nationwide pricing, and they couldn't compare across regions.",[13,2351,2352,2353,2356],{},"What this platform did was ",[16,2354,2355],{},"digitize the entire magazine, issue by issue, and put it online"," as a B2B platform so that any travel agency in the country could browse the latest prices in real time. Simple in concept. Very much not simple in execution.",[54,2358,2360],{"id":2359},"the-real-challenge-was-volume-and-window","The real challenge was \"volume\" and \"window\"",[13,2362,2363],{},"The business shape of the project made its technical characteristics extreme:",[83,2365,2366,2376,2386,2395],{},[86,2367,2368,2371,2372,2375],{},[16,2369,2370],{},"High page density."," A provincial weekly typically ran ",[16,2373,2374],{},"200–300 pages",", and every page was a print-quality high-resolution sliced image (users needed to zoom in to read the fine print on pricing tables).",[86,2377,2378,2381,2382,2385],{},[16,2379,2380],{},"Synchronized nationwide publishing."," The platform covered ",[16,2383,2384],{},"25 branch offices",", all publishing on the same weekly cadence.",[86,2387,2388,2391,2392,133],{},[16,2389,2390],{},"Image volume per week."," Roughly ",[16,2393,2394],{},"5,000–7,500 high-resolution images",[86,2396,2397,2400,2401,2404,2405,2408],{},[16,2398,2399],{},"Extremely tight publishing window."," Every branch finalized their design on ",[16,2402,2403],{},"Thursday and Friday",". All of it had to be digitized and live by ",[16,2406,2407],{},"Saturday and Sunday",", ready for Monday's business open.",[13,2410,2411,2412],{},"In other words — ",[16,2413,2414],{},"a 48-hour weekend window to ingest a TB-scale pile of images accumulated over the week, with zero room for errors or missing pages.",[13,2416,2417],{},"Under that cadence, \"manual upload\" was a dead end from day one. If we had relied on operations staff clicking through an upload UI, we would have needed dozens of people pulling weekend overtime across 25 branches — and if even one person uploaded the wrong page number, downstream agencies would be looking at mismatched pricing sheets. The cost model didn't work and the error rate didn't work.",[54,2419,2421],{"id":2420},"my-core-solution-an-automated-pipeline","My core solution: an automated pipeline",[13,2423,2424,2425,2428,2429,2432],{},"The technical heart of this project wasn't the frontend reader, and wasn't the database itself — it was ",[16,2426,2427],{},"an automated import and distribution pipeline that I designed and wrote myself",". The goal was singular: ",[16,2430,2431],{},"keep humans away from the servers."," Once the designers finished a page and named and organized the files per convention, the tooling did the rest.",[13,2434,2435],{},"Four key design decisions, broken out below.",[1587,2437,2439],{"id":2438},"_1-standardized-directory-to-schema-mapping","1. Standardized directory-to-schema mapping",[13,2441,2442],{},"The earliest version was the most naive one you can imagine — a web page where operations staff clicked \"upload\" one page at a time. It broke weekly.",[13,2444,2445,2446,2449,2450,2453],{},"What we did next was: ",[16,2447,2448],{},"turn the directory structure itself into a protocol."," Operations just had to name page numbers per convention and organize each issue into the prescribed hierarchy (province \u002F issue \u002F page). Once the import tool kicked off, it would ",[16,2451,2452],{},"automatically walk the entire directory tree",", parse out \"this is province X, issue Y, page Z, belonging to section W,\" and write the metadata straight into the database.",[13,2455,2456,2457,2460],{},"There's nothing technically profound about this design. But it converted ",[16,2458,2459],{},"uploading"," from \"manual data entry\" into a \"filesystem convention.\" Once that convention was in place, the operations team's job shifted from \"clicking a mouse\" to \"organizing folders\" — and the latter is scriptable, while the former isn't.",[13,2462,2463,2464,2467],{},"Today we'd call this thinking ",[16,2465,2466],{},"convention over configuration",". I didn't have the vocabulary back then — I just had an intuition that \"conventions should replace input.\" In hindsight, this is the single decision that kept the whole system alive for ten years.",[1587,2469,2471],{"id":2470},"_2-renaming-files-by-database-primary-key-a-lesson-in-indexing","2. Renaming files by database primary key: a lesson in indexing",[13,2473,2474,2475],{},"Not long after the first version shipped, we hit a performance problem: ",[16,2476,2477],{},"magazine page loads got progressively slower, and query latency degraded non-linearly as the data grew.",[13,2479,2480],{},"The root cause was a classic lazy design I'd made early on — I was joining on filenames directly (the kind with Chinese characters and issue numbers baked in), which meant every single page load triggered a fuzzy match. Combined with an unoptimized SQL Server indexing strategy at the time, I'd walked myself straight into a performance pit.",[13,2482,2483,2484,2487],{},"When I rewrote it, I made one key change: ",[16,2485,2486],{},"during import, the program renames high-res originals according to the database primary key (PK) and re-establishes the relationship."," The physical filename on disk becomes a plain integer ID.",[13,2489,2490],{},"The payoff was multi-dimensional:",[83,2492,2493,2496,2499],{},[86,2494,2495],{},"Database indexes now ran entirely on integer PKs. Queries stabilized at millisecond latency.",[86,2497,2498],{},"Filenames no longer broke when issue numbers or section names changed.",[86,2500,2501],{},"Cross-server image migrations became trivial — since filenames carried no business semantics, migration scripts were extremely simple.",[13,2503,2504],{},"Today this reads as textbook \"don't let business semantics contaminate your storage layer.\" But around 2010, I only learned it after performance slapped me in the face.",[1587,2506,2508],{"id":2507},"_3-distributed-file-storage-and-pipelined-distribution","3. Distributed file storage and pipelined distribution",[13,2510,2511,2512,2515],{},"A single file server was never going to handle 25 provinces writing simultaneously — you didn't need a load test to predict that. So from the start I designed the storage as a ",[16,2513,2514],{},"clustered distribution model",": after the import tool renamed a file, it would route the image to a designated file server per rules, and the database only stored \"which server, which path.\"",[13,2517,2518],{},"A few decisions that held up well:",[83,2520,2521,2527,2533],{},[86,2522,2523,2526],{},[16,2524,2525],{},"The upload logic was a pipeline"," (scan → parse → commit → rename → distribute). Each stage retried independently, and a failure at any step wouldn't roll back the whole batch.",[86,2528,2529,2532],{},[16,2530,2531],{},"Sharded by province."," The 25 provinces' uploads didn't block each other, so they could all run in parallel during the weekend window.",[86,2534,2535,2538],{},[16,2536,2537],{},"Image servers were fully separated from application servers."," The frontend served images from a dedicated domain, so the business IIS instances never got crushed by image traffic.",[13,2540,2541,2542,2545,2546,2549],{},"The end result was this: ",[16,2543,2544],{},"the 25 branches' massive image volumes could be fully ingested and distributed within the 48-hour window",", with cumulative storage exceeding ",[16,2547,2548],{},"1TB",". That number is nothing by today's standards, but for a mid-sized business system in China circa 2010, it was substantial.",[1587,2551,2553],{"id":2552},"_4-the-frontend-reader-hd-zoom-drag-in-2010","4. The frontend reader: \"HD zoom + drag\" in 2010",[13,2555,2556,2557,2560],{},"This part was built on ",[16,2558,2559],{},"jQuery"," — yes, that jQuery. React didn't exist yet, and Vue hadn't even been conceived.",[13,2562,2563,2564,2567],{},"The frontend challenge was this: travel pricing sheets are information-dense. Small fonts, tight tables, lots of footnotes. Agency users had to be able to ",[16,2565,2566],{},"freely zoom and pan around a full magazine page"," to actually read the pricing and departure dates for a specific tour.",[13,2569,2570],{},"Key implementation decisions at the time:",[83,2572,2573,2579,2585],{},[86,2574,2575,2578],{},[16,2576,2577],{},"Tiered loading."," Thumbnails shipped first, full-resolution images loaded on demand. We never dumped a multi-megabyte image into the browser unprompted.",[86,2580,2581,2584],{},[16,2582,2583],{},"Zoom and drag implemented via CSS transforms",", not image resampling — which kept things smooth even at high zoom levels.",[86,2586,2587,2590],{},[16,2588,2589],{},"Gesture hit areas and inertia were hand-rolled",", because the jQuery ecosystem had no usable component for this at the time.",[13,2592,2593],{},"All of this is a one-line npm install today. Back then, it was written line by line. Looking at that code now, there's plenty I'd optimize — but it held up in production for ten years without a major incident.",[54,2595,2597],{"id":2596},"why-it-ran-for-ten-years","Why it ran for ten years",[13,2599,2600],{},"When we shut the platform down in 2019, I found myself asking the question too: how does a system written in ASPX + jQuery manage to serve travel agencies across China reliably for a decade?",[13,2602,2603,2604],{},"My after-the-fact conclusion: ",[16,2605,2606],{},"the tech stack wasn't what kept it alive. The architectural constraints were.",[13,2608,2609],{},"Specifically:",[83,2611,2612,2618,2624,2630],{},[86,2613,2614,2617],{},[16,2615,2616],{},"Turning uploads into a filesystem convention"," sidestepped the most fragile part of the human workflow.",[86,2619,2620,2623],{},[16,2621,2622],{},"Keeping business semantics out of filenames"," meant no naming-convention change on the business side ever contaminated the storage layer.",[86,2625,2626,2629],{},[16,2627,2628],{},"Province-level sharding plus a file server cluster"," made peak-window load horizontally scalable by default.",[86,2631,2632,2635],{},[16,2633,2634],{},"Separating the read and write paths"," meant production read traffic was never at the mercy of the operations-side upload surge.",[13,2637,2638],{},"All of this reads like common sense today. But at the time, each of these was a decision I pushed through the team only after \"the last incident\" had forced the point.",[54,2640,2642],{"id":2641},"looking-back-some-honest-reflections","Looking back, some honest reflections",[13,2644,2645],{},"Truthfully, there's plenty about this project I wouldn't do the same way now:",[83,2647,2648,2654,2660,2666],{},[86,2649,2650,2653],{},[16,2651,2652],{},"ASPX was a liability."," Every time we needed to add APIs or integrate with mobile later on, we had to work around it. If I'd had the courage to fully rearchitect toward a Web API + decoupled frontend in 2014, the following five years would have been considerably easier.",[86,2655,2656,2659],{},[16,2657,2658],{},"Too much got built in-house instead of using open source."," The gesture interaction layer in the frontend reader had mature alternatives by 2013, but \"it works\" kept us from revisiting it. That's a textbook form of implicit technical debt.",[86,2661,2662,2665],{},[16,2663,2664],{},"SQL Server's vertical scaling was straining hard by the later years."," I underestimated read\u002Fwrite separation and sharding approaches at the time, and I missed the best window to refactor.",[86,2667,2668,2671],{},[16,2669,2670],{},"The monitoring story was effectively non-existent."," A lot of issues were first reported by operations staff calling in — which would be unacceptable today.",[13,2673,2674,2675],{},"But some things held up — especially that automation pipeline. Ten years of stable operation proves one thing: ",[16,2676,2677],{},"if you can establish the right constraints at the process level, even an aging tech stack can sustain a system for a very long time.",[54,2679,2681],{"id":2680},"a-closing-thought","A closing thought",[13,2683,2684,2685,2688],{},"The platform wound down in 2019, partly because the pandemic hit the travel industry and partly for a deeper reason: ",[16,2686,2687],{},"mobile internet had fundamentally changed how travel agencies got information."," DingTalk groups, WeChat groups, and vertical SaaS had absorbed the intermediary \"magazine-online\" role the platform used to play.",[13,2690,2691],{},"It wasn't defeated. It was outgrown by its era. And that's fine. Something served the ten years it was built to serve, witnessed a whole industry's transformation, and got to exit with dignity — that's already a better ending than most engineers can expect.",[13,2693,2694,2695,2698,2699,2702],{},"While writing this retrospective I dug up the old codebase (I still have the backup). Seeing those ",[69,2696,2697],{},"\u003Casp:Repeater>"," and ",[69,2700,2701],{},"$.ajax"," calls again felt slightly surreal. But those 4 AM weekend moments — watching the import jobs for all 25 provinces finally come back green — those actually happened.",[13,2704,2705],{},"Noted. Archived.",[51,2707],{},[51,2709],{},[786,2711,2713],{"id":2712},"十年项目复盘一个全国性旅游-dm-杂志的数字化-b2b-发布平台","十年项目复盘：一个全国性旅游 DM 杂志的数字化 B2B 发布平台",[10,2715,2716],{},[13,2717,2718],{},"项目周期:2006 — 2019\n技术栈:.NET (ASPX) \u002F SQL Server \u002F jQuery \u002F 自研自动化同步工具 \u002F 分布式文件存储",[10,2720,2721],{},[13,2722,45,2723],{},[47,2724,2725],{},"English version below — scroll down for the English translation.",[51,2727],{},[54,2729,2730],{"id":2730},"写在前面",[13,2732,2733],{},"这是一个做了十年、然后在 2019 年随着疫情和行业转型一起关掉的项目。现在回头看,它身上带着非常典型的\"移动互联网前夜\"的时代痕迹:ASPX、jQuery、手写 SQL、文件服务器集群——没有一个是今天会被写进技术选型第一页的名字。但它确实稳定跑了十年,也确实解决了一个当时没人解决好的问题。",[13,2735,2736],{},"我想趁还记得细节的时候,把这个项目的工程决策记录下来——不是为了证明它有多先进,而是为了记住当年为什么要那样做。",[54,2738,2739],{"id":2739},"这个项目是干什么的",[13,2741,2742,2743,2746,2747,2750],{},"在移动互联网真正普及之前(尤其是 2009 到 2013 那几年),全国旅行社之间的",[16,2744,2745],{},"报价和线路信息","主要靠一个传统渠道流转:",[16,2748,2749],{},"DM 杂志","(直邮广告杂志)。各省公司每周出一本,把自己的产品、价格、出行日期排成版,印刷出来,寄给同业的旅行社。下游旅行社翻杂志、打电话、定产品。",[13,2752,2753],{},"这套流程有两个非常明显的痛点:",[83,2755,2756,2762],{},[86,2757,2758,2761],{},[16,2759,2760],{},"时效性差",":杂志印出来再寄到全国,最快也是几天后的事,价格可能已经变了。",[86,2763,2764,2767],{},[16,2765,2766],{},"信息孤岛",":一家旅行社只订几本杂志,看不到全国范围的报价,没法横向比价。",[13,2769,2770,2771,2774],{},"这个平台做的事情,就是把 DM 杂志",[16,2772,2773],{},"整本数字化搬到线上",",作为一个 B2B 平台让全国旅行社都能实时查阅。听起来简单,做起来是另一回事。",[54,2776,2778],{"id":2777},"真正的挑战在量和窗口上","真正的挑战在\"量\"和\"窗口\"上",[13,2780,2781],{},"项目的业务形态决定了它的技术特征非常极端:",[83,2783,2784,2794,2804,2813],{},[86,2785,2786,2789,2790,2793],{},[16,2787,2788],{},"页数密度大",":一个省份的周刊常规在 ",[16,2791,2792],{},"200–300 页",",每一页都是高清印刷级切片图(因为要能放大看清报价单的小字)。",[86,2795,2796,2799,2800,2803],{},[16,2797,2798],{},"全国同步出刊",":覆盖 ",[16,2801,2802],{},"25 个分公司",",每周同时出刊。",[86,2805,2806,2809,2810,899],{},[16,2807,2808],{},"单周图片总量",":约 ",[16,2811,2812],{},"5,000–7,500 张高清大图",[86,2814,2815,2818,2819,2822,2823,2826],{},[16,2816,2817],{},"发布窗口极窄",":所有分公司都是",[16,2820,2821],{},"周四、周五","完成设计出稿,",[16,2824,2825],{},"周六、周日","必须全部数字化上线,以便周一业务开盘。",[13,2828,2829,2830,899],{},"换句话说——",[16,2831,2832],{},"周末 48 小时的窗口,要吞掉一周里堆起来的 TB 级图片,并且不能出错、不能漏页",[13,2834,2835],{},"这种业务节奏下,\"人工上传\"这条路从第一天就是走不通的。如果靠运营同学点页面按钮传图,25 个分公司至少要配几十号人同时加班,而且只要有一个人传错页码,下游旅行社看到的报价单就会对不上。成本和错误率都不现实。",[54,2837,2839],{"id":2838},"我的核心方案一条自动化流水线","我的核心方案:一条自动化流水线",[13,2841,2842,2843,2846,2847,2850],{},"项目的技术核心,不在前端阅读器,也不在数据库本身,而在于",[16,2844,2845],{},"我自己设计并写的一套自动化导入和分发流水线","。目标只有一个:",[16,2848,2849],{},"让人不要碰服务器","。设计师那边出完图,按规范命名和分目录,剩下的事情工具自己做完。",[13,2852,2853],{},"下面拆开讲四个关键设计。",[1587,2855,2857],{"id":2856},"_1-标准化目录映射机制","1. 标准化目录映射机制",[13,2859,2860],{},"最早期做过最朴素的版本——给运营一个网页,让他们一页一页点上传。一周崩一次。",[13,2862,2863,2864,2867,2868,2871],{},"后来做的事情是:",[16,2865,2866],{},"把目录结构本身变成协议","。业务端只要按约定命名页码、把每期杂志整理进规定的目录层级(省份 \u002F 期号 \u002F 页码),导入工具启动后会",[16,2869,2870],{},"自动扫描整棵目录树",",解析出\"这是哪个省、第几期、第几页、对应哪个栏目\",然后把元数据直接写进数据库。",[13,2873,2874,2875,2878],{},"这个设计本身没什么高深技术,但它把",[16,2876,2877],{},"上传","这件事从\"人工录入\"变成了\"文件系统约定\"。一旦约定建立起来,运营同学的工作就从\"点鼠标\"变成\"整理文件夹\"——后者是可以用脚本批处理的,前者不行。",[13,2880,2881,2882,2884],{},"这个选择背后的思路,今天叫 ",[16,2883,2466],{},",当年我没这个词汇,只是凭经验觉得\"让约定去替代输入\"。现在回看,这是整个系统能跑十年的根基。",[1587,2886,2888],{"id":2887},"_2-用数据库主键重命名文件一个关于索引的教训","2. 用数据库主键重命名文件:一个关于索引的教训",[13,2890,2891,2892,899],{},"第一版系统上线没多久就遇到一个性能问题:",[16,2893,2894],{},"页面加载杂志时越来越慢,而且查询延迟随数据量增长呈现非线性恶化",[13,2896,2897],{},"查下来原因是早期做了一个典型的偷懒设计——直接用文件名(带中文和期号的那种)去做关联查找,相当于每次读一页图都要跑一次模糊匹配,加上当时 SQL Server 索引策略没优化好,很快就把自己卷进了性能坑里。",[13,2899,2900,2901,2904],{},"重构的时候做了一个关键调整:",[16,2902,2903],{},"导入过程中,程序会根据数据库主键(PK)对高清原图进行重命名和重新关联","。落到磁盘上的物理文件名就是一串整型 ID。",[13,2906,2907],{},"这个改动带来的收益是多重的:",[83,2909,2910,2913,2916],{},[86,2911,2912],{},"数据库索引完全走整型 PK,查询稳定在毫秒级;",[86,2914,2915],{},"文件名再也不会因为期号、栏目名改动而失效;",[86,2917,2918],{},"跨服务器迁移图片时,文件名本身不携带业务语义,迁移脚本极其简单。",[13,2920,2921],{},"今天看是教科书级别的\"不要让业务语义污染存储层\",但在 2010 年前后,这是我被性能打脸之后才学到的。",[1587,2923,2925],{"id":2924},"_3-分布式文件存储与流水线分发","3. 分布式文件存储与流水线分发",[13,2927,2928,2929,2932],{},"单台文件服务器撑不住 25 个省同时写入——这件事不需要压测就能预判。所以从一开始就把文件存储设计成了",[16,2930,2931],{},"集群分发模式",":导入工具在重命名完文件之后,会按照规则把图片分发到指定的文件服务器上,数据库里只存\"这张图落在哪台服务器的哪个路径\"。",[13,2934,2935],{},"几个当时做得还算对的决策:",[83,2937,2938,2944,2950],{},[86,2939,2940,2943],{},[16,2941,2942],{},"上传逻辑做成流水线","(扫描 → 解析 → 入库 → 重命名 → 分发),每一环可以独立重试,中间任何一步失败都不会让整批数据回滚;",[86,2945,2946,2949],{},[16,2947,2948],{},"按省份做分片",",25 个省的上传互不阻塞,这样周末窗口里大家可以并行跑;",[86,2951,2952,2955],{},[16,2953,2954],{},"图片服务器和应用服务器彻底分离",",前端读图走独立域名,避免压垮业务 IIS。",[13,2957,2958,2959,2962,2963,2965],{},"最终效果是:",[16,2960,2961],{},"25 个分公司的海量图片能在 48 小时的窗口内全部完成入库和分发",",累计数据量超过 ",[16,2964,2548],{},"。这个数字今天看不算什么,但在 2010 年前后的国内中小型业务系统里,是个不小的量。",[1587,2967,2969],{"id":2968},"_4-前端阅读器2010-年的高清缩放-拖拽","4. 前端阅读器:2010 年的\"高清缩放 + 拖拽\"",[13,2971,2972,2973,2975],{},"这一块是基于 ",[16,2974,2559],{}," 做的——对,就是那个 jQuery。当时还没有 React,Vue 连影子都没有。",[13,2977,2978,2979,2982],{},"前端的挑战在于:旅游报价单里的信息非常密,字小、表格密、脚注多。旅行社用户必须能",[16,2980,2981],{},"在一张整页杂志上自由缩放和平移",",才能看清某一条线路的具体报价和发团日期。",[13,2984,2985],{},"当年实现的几个关键点:",[83,2987,2988,2994,3000],{},[86,2989,2990,2993],{},[16,2991,2992],{},"分级加载",":缩略图先出,全尺寸图按需加载,避免一开始就怼一张几 MB 的大图进浏览器;",[86,2995,2996,2999],{},[16,2997,2998],{},"缩放和拖拽基于 CSS transform",",不走图片重采样,保证高清下依然流畅;",[86,3001,3002,3005],{},[16,3003,3004],{},"手势交互的命中区和惯性","手写了一版,因为 jQuery 生态里没有现成的能用的组件。",[13,3007,3008],{},"放在今天这些都是一个开源库解决的事情,但当年是一行一行写出来的。这部分代码现在回看有不少可以优化的地方,但它在生产环境里撑了十年没出大问题。",[54,3010,3011],{"id":3011},"它为什么能跑十年",[13,3013,3014],{},"2019 年关掉这个平台的时候,我自己也想过这个问题:一个用 ASPX + jQuery 写的系统,凭什么能稳定服务全国旅行社十年?",[13,3016,3017,3018,899],{},"我事后的结论是:",[16,3019,3020],{},"技术选型不是它活下来的原因,架构约束是",[13,3022,3023],{},"具体说:",[83,3025,3026,3032,3038,3044],{},[86,3027,3028,3031],{},[16,3029,3030],{},"把上传变成文件系统约定",",绕开了人工环节最脆弱的部分;",[86,3033,3034,3037],{},[16,3035,3036],{},"让文件名不携带业务语义",",任何一次业务侧命名规则变化都不会污染存储层;",[86,3039,3040,3043],{},[16,3041,3042],{},"按省份分片 + 文件服务器集群",",让峰值窗口的压力天然可以横向扩展;",[86,3045,3046,3049],{},[16,3047,3048],{},"读写路径分离",",让业务读请求永远不会被运营侧的上传洪峰影响。",[13,3051,3052],{},"这些东西放在今天都是常识,但当年在团队里推行的时候,其实每一个都是被\"上一次事故\"逼出来的。",[54,3054,3055],{"id":3055},"一些今天回看的反思",[13,3057,3058],{},"老实说,这个项目有很多我现在不会再那样做的地方:",[83,3060,3061,3067,3073,3079],{},[86,3062,3063,3066],{},[16,3064,3065],{},"ASPX 是个负债","。后期业务要加接口、要对接移动端,每次都得绕。如果 2014 年有勇气整体重构到 Web API + 前后端分离,后面五年会轻松很多。",[86,3068,3069,3072],{},[16,3070,3071],{},"自研了太多本该用开源的东西","。前端阅读器那套手势交互,2013 年之后已经有成熟方案了,但因为\"能跑\"就没动过,这是一种隐性技术债。",[86,3074,3075,3078],{},[16,3076,3077],{},"SQL Server 的垂直扩容撑到后期已经很吃力","。当时对读写分离和分库分表的方案评估不足,错过了最佳重构窗口。",[86,3080,3081,3084],{},[16,3082,3083],{},"监控体系几乎等于没有","。很多问题是靠运营电话反馈才发现的,这在今天是不可接受的。",[13,3086,3087,3088,899],{},"但它也有做对的部分——尤其是那条自动化流水线。十年的稳定运行证明:",[16,3089,3090],{},"只要你能在流程上建立起好的约束,哪怕底层技术栈老旧,系统也能活得很久",[54,3092,3093],{"id":3093},"一点感慨",[13,3095,3096,3097,3100],{},"这个平台在 2019 年关停,一部分是因为疫情冲击了整个旅游行业,另一部分更深层的原因是——",[16,3098,3099],{},"移动互联网已经彻底改变了旅行社之间的信息获取方式","。钉钉、微信群、垂直 SaaS 把这个\"杂志线上化\"的中间层需求消化掉了。",[13,3102,3103],{},"它不是被打败的,是被时代翻过去的。这挺好。一个东西服务了它该服务的十年,也见证了一个行业形态的变化,能体面地退场,已经是工程师能期待的不错的结局了。",[13,3105,3106,3107,3109,3110,3112],{},"写这篇复盘的时候翻了一下当年的代码(还有备份),看着那些 ",[69,3108,2697],{}," 和 ",[69,3111,2701],{}," 有点恍惚。但那些在周末通宵盯着 25 个省份图片全部导入成功的凌晨四点,是确实存在过的。",[13,3114,3115],{},"记一笔,存档。",{"title":71,"searchDepth":253,"depth":253,"links":3117},[3118,3119,3120,3121,3127,3128,3129,3130,3131,3132,3133,3139,3140,3141],{"id":2306,"depth":253,"text":2307},{"id":2320,"depth":253,"text":2321},{"id":2359,"depth":253,"text":2360},{"id":2420,"depth":253,"text":2421,"children":3122},[3123,3124,3125,3126],{"id":2438,"depth":264,"text":2439},{"id":2470,"depth":264,"text":2471},{"id":2507,"depth":264,"text":2508},{"id":2552,"depth":264,"text":2553},{"id":2596,"depth":253,"text":2597},{"id":2641,"depth":253,"text":2642},{"id":2680,"depth":253,"text":2681},{"id":2730,"depth":253,"text":2730},{"id":2739,"depth":253,"text":2739},{"id":2777,"depth":253,"text":2778},{"id":2838,"depth":253,"text":2839,"children":3134},[3135,3136,3137,3138],{"id":2856,"depth":264,"text":2857},{"id":2887,"depth":264,"text":2888},{"id":2924,"depth":264,"text":2925},{"id":2968,"depth":264,"text":2969},{"id":3011,"depth":253,"text":3011},{"id":3055,"depth":253,"text":3055},{"id":3093,"depth":253,"text":3093},"https:\u002F\u002Fimages.xtop.dev\u002Fblogs\u002F2026\u002F04\u002Ftravel-dm-platform-retrospective-bilingual.webp","2026-04-21","A ten-year retrospective on building a nationwide B2B platform that digitized weekly travel DM magazines across 25 branches — and why an ASPX + jQuery stack survived a decade.",{},"\u002Fblogs\u002Ftravel-dm-platform-retrospective-bilingual",{"title":2294,"description":3144},"blogs\u002Ftravel-dm-platform-retrospective-bilingual",[3150,3151,3152,3153,3154,3155,3156,3157,3158],"retrospective","system-design","legacy-systems","automation","b2b","distributed-storage","convention-over-configuration",".net","jquery","bWmkB1R3z-PDsF6X4cymuI8s9Ri7jlZORXAGEUKazjI",[3161,3689,4207],{"id":3162,"title":3163,"body":3164,"category":1513,"cover":3676,"date":3677,"description":3678,"draft":1517,"extension":1518,"meta":3679,"navigation":1520,"path":3680,"readingTime":1522,"seo":3681,"stem":3682,"tags":3683,"__hash__":3688},"blogs\u002Fblogs\u002Fbuilding-xtop-dev.md","Building xtop.dev in Three Days: What AI Got Right and Wrong",{"type":7,"value":3165,"toc":3653},[3166,3169,3172,3175,3179,3182,3185,3196,3202,3206,3209,3245,3248,3254,3260,3270,3274,3280,3289,3295,3298,3302,3305,3309,3312,3319,3322,3329,3340,3355,3359,3366,3369,3373,3376,3380,3383,3390,3393,3398,3402,3405,3422,3425,3432,3436,3439,3442,3446,3449,3453,3456,3459,3462,3466,3469,3475,3479,3545,3548,3552,3555,3567,3573,3579,3593,3596,3600,3603,3635,3640,3643,3645],[13,3167,3168],{},"Three days ago, this site was a Dynadot parking page. Today it's a full personal site — six pages, a content system, a working contact form backed by a real .NET API, deployed across Cloudflare Pages and a $5 VPS.",[13,3170,3171],{},"I built most of it pairing with Claude.",[13,3173,3174],{},"This isn't another \"AI wrote my code!\" post. It's more honest than that — a build log of what AI actually helped with, what it got wrong, and the decisions I still had to make myself.",[54,3176,3178],{"id":3177},"why-bother-with-a-personal-site","Why bother with a personal site",[13,3180,3181],{},"I've been freelancing on and off for a few years. Most of my leads came through referrals and a pinned GitHub repo. That worked, but it put a ceiling on who could find me.",[13,3183,3184],{},"A personal site does three things a GitHub profile can't:",[1854,3186,3187,3190,3193],{},[86,3188,3189],{},"It tells a story, not just a resume",[86,3191,3192],{},"It ranks for long-tail searches (\"nuxt dotnet freelance developer\")",[86,3194,3195],{},"It's where cold leads can actually reach you",[13,3197,3198,3199,133],{},"The goal wasn't perfection. It was ",[16,3200,3201],{},"something real, shipped, that represents me",[54,3203,3205],{"id":3204},"the-stack-and-why","The stack, and why",[13,3207,3208],{},"I picked the stack before talking to the AI. This matters. If you let the AI pick, you get whatever is trendy in its training data.",[83,3210,3211,3217,3227,3233,3239],{},[86,3212,3213,3216],{},[16,3214,3215],{},"Frontend",": Nuxt 4 + Nuxt UI 4 + Tailwind v4 + TypeScript",[86,3218,3219,3222,3223,3226],{},[16,3220,3221],{},"Content",": ",[69,3224,3225],{},"@nuxt\u002Fcontent"," v3 (Markdown-driven blog, projects, travel)",[86,3228,3229,3232],{},[16,3230,3231],{},"Backend",": .NET 8 Minimal API (just for the contact form)",[86,3234,3235,3238],{},[16,3236,3237],{},"Hosting",": Cloudflare Pages (frontend) + $5 VPS (backend)",[86,3240,3241,3244],{},[16,3242,3243],{},"Images",": Cloudflare R2 with a custom domain",[13,3246,3247],{},"Three constraints drove these choices:",[13,3249,3250,3253],{},[16,3251,3252],{},"It should stay cheap."," Total cost is around $5\u002Fmonth. Cloudflare Pages and R2 are effectively free at my scale.",[13,3255,3256,3259],{},[16,3257,3258],{},"It should show what I can actually do."," Having a real .NET backend on a real VPS — even just for a contact form — is a live demo of my full-stack work. A static-only site can't claim that.",[13,3261,3262,3265,3266,3269],{},[16,3263,3264],{},"It should be fun to maintain."," All content lives in Markdown files inside ",[69,3267,3268],{},"content\u002F",". Writing a blog post is just opening VSCode and typing. No CMS. No database. No admin panel.",[54,3271,3273],{"id":3272},"three-days-roughly","Three days, roughly",[13,3275,3276,3279],{},[16,3277,3278],{},"Day 1 — Scaffolding and structure."," Six pages wired up with placeholder content. Design tokens locked in: Playfair Display serif, teal palette, lots of whitespace. The shape of the site was visible by evening.",[13,3281,3282,3285,3286,3288],{},[16,3283,3284],{},"Day 2 — Content system and backend."," Migrated mock data into ",[69,3287,3225],{}," across three collections (blogs, projects, travel). Built the .NET 8 contact API with SMTP email delivery and rate limiting. Wired the form to the backend.",[13,3290,3291,3294],{},[16,3292,3293],{},"Day 3 — Polish and ship."," Error page, SEO meta tags, mobile breakpoints, image pipeline to R2, DNS, SSL, deployment. All the finish-tax work that turns a prototype into a live site.",[13,3296,3297],{},"No weekend work magic. Just focused days with a collaborator that never slept.",[54,3299,3301],{"id":3300},"where-ai-actually-helped","Where AI actually helped",[13,3303,3304],{},"Let me be specific, because \"AI helped me build this\" usually means nothing.",[1587,3306,3308],{"id":3307},"scaffolding-that-would-have-taken-a-full-day-took-two-hours","Scaffolding that would have taken a full day took two hours",[13,3310,3311],{},"I described the six pages I wanted with rough layouts. Claude generated the initial Vue files with proper structure, placeholder content, and consistent styling tokens. I reviewed, caught inconsistencies, and iterated.",[13,3313,3314,3315,3318],{},"This is the sweet spot for AI right now — ",[16,3316,3317],{},"bulk generation of familiar patterns",". It knows what a blog list page looks like. It knows how Nuxt pages are structured. It can write fifteen similar-but-not-identical components in ten minutes.",[13,3320,3321],{},"Doing this by hand would have been a full day of copy-paste-modify. I'd have been bored. I'd have introduced bugs.",[1587,3323,3325,3326,3328],{"id":3324},"migration-from-mock-data-to-nuxtcontent-was-painless","Migration from mock data to ",[69,3327,3225],{}," was painless",[13,3330,3331,3332,3335,3336,3339],{},"I started Day 1 with hardcoded arrays of blog posts in a composable. Day 2 morning, I wanted Markdown files instead. I described the migration in two sentences. Claude rewrote the pages to use ",[69,3333,3334],{},"queryCollection()",", added the right schema to ",[69,3337,3338],{},"content.config.ts",", and walked me through the directory structure.",[13,3341,3342,3343,3346,3347,3350,3351,3354],{},"The migration across three collections took about thirty minutes. The nested travel structure (",[69,3344,3345],{},"content\u002Ftravel\u002Fjapan\u002Fxxx.md",") needed a judgment call — I wanted ",[69,3348,3349],{},"\u002Ftravel\u002F[slug]"," routing instead of ",[69,3352,3353],{},"\u002Ftravel\u002F[country]\u002F[slug]",", which forced a different query pattern. I made that call. Claude implemented it.",[1587,3356,3358],{"id":3357},"debugging-the-weird-stuff","Debugging the weird stuff",[13,3360,3361,3362,3365],{},"On Day 3 I hit a Vue 3 hydration mismatch warning that seemed unrelated to anything I'd just changed. I pasted the stack trace. Claude diagnosed it as an SSR issue with my scroll-reveal composable — CSS was applying ",[69,3363,3364],{},"opacity: 0"," during hydration, before the IntersectionObserver could kick in, creating a mismatch between the server HTML and the hydrated state.",[13,3367,3368],{},"The fix was subtle: move the \"hidden state\" from CSS to a JS-applied class, so the server HTML matched what the client first renders. I wouldn't have figured that out alone in under an hour. Claude found it in one exchange.",[54,3370,3372],{"id":3371},"where-ai-didnt-help","Where AI didn't help",[13,3374,3375],{},"Equally specific, because this matters more.",[1587,3377,3379],{"id":3378},"taste","Taste",[13,3381,3382],{},"Claude can generate a hero section. It cannot tell me whether a serif font for headings is right for this site.",[13,3384,3385,3386,3389],{},"That decision — Playfair Display serif, teal palette, lots of whitespace — was mine. The AI can execute a design. It cannot make the design ",[47,3387,3388],{},"feel"," like me.",[13,3391,3392],{},"Every choice that touched identity was mine: the color palette, the typography, the copywriting voice, the \"Lost at sea\" tone on the 404 page. When I let Claude write copy, it came back as generic marketing-speak. When I wrote the copy and asked Claude to polish tone, it was better.",[13,3394,3395],{},[16,3396,3397],{},"AI is a great executor, a poor art director.",[1587,3399,3401],{"id":3400},"deciding-what-not-to-build","Deciding what not to build",[13,3403,3404],{},"In three days, the discipline to cut matters more than the speed to build. I considered adding:",[83,3406,3407,3410,3413,3416,3419],{},[86,3408,3409],{},"A newsletter signup",[86,3411,3412],{},"Comments on blog posts",[86,3414,3415],{},"An analytics dashboard",[86,3417,3418],{},"A WebGL-animated hero section",[86,3420,3421],{},"i18n for English + Chinese",[13,3423,3424],{},"I built none of them. Not one.",[13,3426,3427,3428,3431],{},"Each would have added a day of work and zero client acquisition. Claude would have happily helped me build all five. The discipline to ",[16,3429,3430],{},"cut"," features is a human skill. AI defaults to \"yes, let's build that\" because it has no stake in my time.",[1587,3433,3435],{"id":3434},"product-positioning","Product positioning",[13,3437,3438],{},"The hardest part of the site isn't the code. It's the Services page — deciding what I offer, what I don't, what pricing tier I show, what process I describe.",[13,3440,3441],{},"I spent more time on that one page than on any other. Claude helped me draft and iterate, but the actual choices — \"I don't do WordPress sites\", \"I'm fine with three-week projects but not three-month ones\", \"my minimum is $1,000\" — those were me reading my own calendar and bank account.",[54,3443,3445],{"id":3444},"the-parts-nobody-talks-about","The parts nobody talks about",[13,3447,3448],{},"Two surprises worth flagging if you're about to do something similar:",[1587,3450,3452],{"id":3451},"the-finish-tax","The \"finish\" tax",[13,3454,3455],{},"Day 3 took longer than Day 1 and Day 2 combined. Error pages, SEO meta tags, mobile breakpoints, the contact form rate limiter, the hydration bug, the R2 image upload pipeline, the deployment guide, the DNS configuration, the SSL renewal cron.",[13,3457,3458],{},"None of this is glamorous. All of it is required.",[13,3460,3461],{},"If you've built a side project and felt like you were \"almost done\" for the last stretch — yeah, you were. The finish tax is real, and it doesn't care how fast the scaffolding went.",[1587,3463,3465],{"id":3464},"the-deployment-rabbit-hole","The deployment rabbit hole",[13,3467,3468],{},"I initially tried Cloudflare Workers because it sounded fancier. Spent a morning on it before I realized it was the wrong tool — I didn't need edge compute, I needed static hosting. Switched to Cloudflare Pages and was deployed in ten minutes.",[13,3470,3471,3474],{},[16,3472,3473],{},"Sometimes the answer is \"use the simpler thing.\""," AI tends to suggest more powerful tools than you need. Push back.",[54,3476,3478],{"id":3477},"what-it-cost","What it cost",[1592,3480,3481,3491],{},[1595,3482,3483],{},[1598,3484,3485,3488],{},[1601,3486,3487],{},"Item",[1601,3489,3490],{},"Monthly",[1610,3492,3493,3501,3509,3517,3525,3533],{},[1598,3494,3495,3498],{},[1615,3496,3497],{},"Domain (xtop.dev)",[1615,3499,3500],{},"~$0.60 (amortized)",[1598,3502,3503,3506],{},[1615,3504,3505],{},"VPS ($5\u002Fmonth, for .NET API)",[1615,3507,3508],{},"$5.00",[1598,3510,3511,3514],{},[1615,3512,3513],{},"Cloudflare Pages",[1615,3515,3516],{},"$0 (free tier, unlimited bandwidth)",[1598,3518,3519,3522],{},[1615,3520,3521],{},"Cloudflare R2 (image storage)",[1615,3523,3524],{},"$0 (under 10GB)",[1598,3526,3527,3530],{},[1615,3528,3529],{},"SMTP (QQ Mail for contact form)",[1615,3531,3532],{},"$0",[1598,3534,3535,3540],{},[1615,3536,3537],{},[16,3538,3539],{},"Total",[1615,3541,3542],{},[16,3543,3544],{},"~$5.60\u002Fmonth",[13,3546,3547],{},"First client inquiry pays for 10+ years of hosting.",[54,3549,3551],{"id":3550},"would-i-do-it-again-this-way","Would I do it again this way?",[13,3553,3554],{},"Mostly, yes.",[13,3556,3557,3560,3561,3563,3564,133],{},[16,3558,3559],{},"Kept",": Nuxt + ",[69,3562,3225],{}," + Cloudflare Pages. Markdown-driven sites are a joy to maintain. Every new post is one file, one ",[69,3565,3566],{},"git push",[13,3568,3569,3572],{},[16,3570,3571],{},"Kept with caveats",": The .NET backend. Overkill for just a contact form, but it's a live portfolio piece — \"this developer can actually deploy a Linux VPS with Nginx and systemd\" — that a Vercel serverless function can't replicate.",[13,3574,3575,3578],{},[16,3576,3577],{},"Would change",": I'd pick the typography and color palette in the first hour instead of drifting into it. Deciding design late caused rework. On a three-day timeline, two hours of rework is a lot.",[13,3580,3581,3584,3585,3588,3589,3592],{},[16,3582,3583],{},"Surprised me",": How much AI helped with the ",[47,3586,3587],{},"boring"," parts (scaffolding, refactors, deployment scripts) versus the ",[47,3590,3591],{},"interesting"," parts (design, positioning, voice). The boring parts are where AI shines. The interesting parts stay human.",[13,3594,3595],{},"Three days is possible because AI compresses the boring parts. It doesn't compress the interesting ones.",[54,3597,3599],{"id":3598},"if-youre-thinking-of-doing-this","If you're thinking of doing this",[13,3601,3602],{},"My suggestions, in order of importance:",[1854,3604,3605,3611,3617,3623,3629],{},[86,3606,3607,3610],{},[16,3608,3609],{},"Pick your stack before you open the AI chat."," Otherwise you'll build a trendy stack instead of one you'll enjoy maintaining.",[86,3612,3613,3616],{},[16,3614,3615],{},"Ship one version end-to-end before you polish."," My first version was ugly. Shipping it unlocked every subsequent decision.",[86,3618,3619,3622],{},[16,3620,3621],{},"Write your own copy."," Let AI edit it.",[86,3624,3625,3628],{},[16,3626,3627],{},"Cut features ruthlessly."," A focused site out-performs a bloated one.",[86,3630,3631,3634],{},[16,3632,3633],{},"Get to the contact form fast."," Everything upstream of the contact form is scaffolding for the contact form.",[13,3636,3637,3638,133],{},"The site took three days. It works. People can reach me. I can write new posts in Markdown and ",[69,3639,3566],{},[13,3641,3642],{},"That's the whole pitch.",[51,3644],{},[13,3646,3647],{},[47,3648,3649,3650,133],{},"Thanks for reading. If this resonated and you need a developer who pairs well with AI but still makes the hard calls himself, ",[1899,3651,3652],{"href":1901},"my inbox is open",{"title":71,"searchDepth":253,"depth":253,"links":3654},[3655,3656,3657,3658,3664,3669,3673,3674,3675],{"id":3177,"depth":253,"text":3178},{"id":3204,"depth":253,"text":3205},{"id":3272,"depth":253,"text":3273},{"id":3300,"depth":253,"text":3301,"children":3659},[3660,3661,3663],{"id":3307,"depth":264,"text":3308},{"id":3324,"depth":264,"text":3662},"Migration from mock data to @nuxt\u002Fcontent was painless",{"id":3357,"depth":264,"text":3358},{"id":3371,"depth":253,"text":3372,"children":3665},[3666,3667,3668],{"id":3378,"depth":264,"text":3379},{"id":3400,"depth":264,"text":3401},{"id":3434,"depth":264,"text":3435},{"id":3444,"depth":253,"text":3445,"children":3670},[3671,3672],{"id":3451,"depth":264,"text":3452},{"id":3464,"depth":264,"text":3465},{"id":3477,"depth":253,"text":3478},{"id":3550,"depth":253,"text":3551},{"id":3598,"depth":253,"text":3599},"https:\u002F\u002Fimages.xtop.dev\u002Fblogs\u002F2026\u002F04\u002Fbuilding-xtop-dev.jpg","2026-04-20","A build log of shipping my personal developer site in 72 hours with Nuxt 4, .NET 8, and an AI collaborator. What worked, what didn't, and what I'd do differently.",{},"\u002Fblogs\u002Fbuilding-xtop-dev",{"title":3163,"description":3678},"blogs\u002Fbuilding-xtop-dev",[3684,3685,2287,3686,3687],"nuxt","dotnet","indie-dev","build-log","KO_zwwpqIxYIBaRJQFb0s1UShrLYr6m8C73Oq0-9yv4",{"id":1537,"title":1538,"body":3690,"category":1513,"cover":1547,"date":2279,"description":2280,"draft":1517,"extension":1518,"meta":4204,"navigation":1520,"path":2282,"readingTime":1522,"seo":4205,"stem":2284,"tags":4206,"__hash__":2291},{"type":7,"value":3691,"toc":4174},[3692,3696,3702,3704,3706,3708,3710,3712,3714,3718,3720,3722,3724,3768,3770,3772,3778,3780,3784,3786,3788,3790,3792,3796,3810,3814,3816,3818,3820,3824,3826,3844,3848,3850,3852,3856,3858,3872,3874,3876,3878,3880,3894,3896,3898,3900,3914,3916,3918,3922,3924,3926,3932,3934,3936,3938,3944,3946,3948,3950,3952,3954,3956,3960,3962,3964,3966,4010,4012,4014,4020,4022,4026,4028,4030,4032,4034,4038,4052,4056,4058,4060,4062,4066,4068,4086,4090,4092,4094,4098,4100,4114,4116,4118,4120,4122,4136,4138,4140,4142,4156,4158,4160,4164,4166,4168],[13,3693,3694],{},[1544,3695],{"alt":1546,"src":1547},[10,3697,3698],{},[13,3699,45,3700],{},[47,3701,49],{},[51,3703],{},[13,3705,1558],{},[13,3707,1561],{},[54,3709,1565],{"id":1564},[13,3711,1568],{},[13,3713,1571],{},[13,3715,1574,3716,1578],{},[16,3717,1577],{},[54,3719,1582],{"id":1581},[13,3721,1585],{},[1587,3723,1590],{"id":1589},[1592,3725,3726,3736],{},[1595,3727,3728],{},[1598,3729,3730,3732,3734],{},[1601,3731],{},[1601,3733,1605],{},[1601,3735,1608],{},[1610,3737,3738,3748,3758],{},[1598,3739,3740,3744,3746],{},[1615,3741,3742],{},[16,3743,1619],{},[1615,3745,1622],{},[1615,3747,1625],{},[1598,3749,3750,3754,3756],{},[1615,3751,3752],{},[16,3753,1632],{},[1615,3755,1635],{},[1615,3757,1638],{},[1598,3759,3760,3764,3766],{},[1615,3761,3762],{},[16,3763,1645],{},[1615,3765,1648],{},[1615,3767,1651],{},[13,3769,1654],{},[1587,3771,1658],{"id":1657},[13,3773,1661,3774,1665,3776,1669],{},[47,3775,1664],{},[16,3777,1668],{},[13,3779,1672],{},[13,3781,1675,3782],{},[16,3783,1678],{},[1587,3785,1682],{"id":1681},[13,3787,1685],{},[54,3789,1689],{"id":1688},[13,3791,1692],{},[13,3793,1695,3794,227],{},[16,3795,1698],{},[83,3797,3798,3802,3806],{},[86,3799,3800,1706],{},[16,3801,1705],{},[86,3803,3804,1712],{},[16,3805,1711],{},[86,3807,3808,1718],{},[16,3809,1717],{},[13,3811,3812,1724],{},[16,3813,1723],{},[54,3815,1728],{"id":1727},[1587,3817,1732],{"id":1731},[13,3819,1735],{},[13,3821,1738,3822,1742],{},[16,3823,1741],{},[13,3825,1745],{},[83,3827,3828,3832,3836,3840],{},[86,3829,3830,1753],{},[16,3831,1752],{},[86,3833,3834,1759],{},[16,3835,1758],{},[86,3837,3838,1765],{},[16,3839,1764],{},[86,3841,3842,1771],{},[16,3843,1770],{},[13,3845,1774,3846],{},[16,3847,1777],{},[1587,3849,1781],{"id":1780},[13,3851,1784],{},[13,3853,3854],{},[16,3855,1789],{},[13,3857,1792],{},[83,3859,3860,3864,3868],{},[86,3861,3862,1800],{},[16,3863,1799],{},[86,3865,3866,1806],{},[16,3867,1805],{},[86,3869,3870,1812],{},[16,3871,1811],{},[13,3873,1815],{},[1587,3875,1819],{"id":1818},[13,3877,1822],{},[13,3879,1825],{},[83,3881,3882,3886,3890],{},[86,3883,3884],{},[16,3885,1832],{},[86,3887,3888],{},[16,3889,1837],{},[86,3891,3892],{},[16,3893,1842],{},[13,3895,1845],{},[54,3897,1849],{"id":1848},[13,3899,1852],{},[1854,3901,3902,3906,3910],{},[86,3903,3904,1861],{},[16,3905,1860],{},[86,3907,3908,1867],{},[16,3909,1866],{},[86,3911,3912,1873],{},[16,3913,1872],{},[54,3915,1877],{"id":1876},[13,3917,1880],{},[13,3919,1883,3920,1887],{},[16,3921,1886],{},[13,3923,1890],{},[51,3925],{},[13,3927,3928],{},[47,3929,1897,3930,133],{},[1899,3931,1902],{"href":1901},[51,3933],{},[51,3935],{},[786,3937,1910],{"id":1909},[10,3939,3940],{},[13,3941,45,3942],{},[47,3943,824],{},[51,3945],{},[13,3947,1921],{},[13,3949,1924],{},[54,3951,1928],{"id":1927},[13,3953,1931],{},[13,3955,1934],{},[13,3957,1937,3958,1941],{},[16,3959,1940],{},[54,3961,1945],{"id":1944},[13,3963,1948],{},[1587,3965,1952],{"id":1951},[1592,3967,3968,3978],{},[1595,3969,3970],{},[1598,3971,3972,3974,3976],{},[1601,3973],{},[1601,3975,1963],{},[1601,3977,1608],{},[1610,3979,3980,3990,4000],{},[1598,3981,3982,3986,3988],{},[1615,3983,3984],{},[16,3985,1974],{},[1615,3987,1977],{},[1615,3989,1980],{},[1598,3991,3992,3996,3998],{},[1615,3993,3994],{},[16,3995,1987],{},[1615,3997,1990],{},[1615,3999,1993],{},[1598,4001,4002,4006,4008],{},[1615,4003,4004],{},[16,4005,2000],{},[1615,4007,2003],{},[1615,4009,2006],{},[13,4011,2009],{},[1587,4013,2013],{"id":2012},[13,4015,2016,4016,2019,4018,2022],{},[47,4017,1664],{},[16,4019,1668],{},[13,4021,2025],{},[13,4023,2028,4024],{},[16,4025,2031],{},[1587,4027,2035],{"id":2034},[13,4029,2038],{},[54,4031,2042],{"id":2041},[13,4033,2045],{},[13,4035,2048,4036,993],{},[16,4037,2051],{},[83,4039,4040,4044,4048],{},[86,4041,4042,2058],{},[16,4043,1705],{},[86,4045,4046,2063],{},[16,4047,1711],{},[86,4049,4050,2068],{},[16,4051,1717],{},[13,4053,4054,2074],{},[16,4055,2073],{},[54,4057,2078],{"id":2077},[1587,4059,2082],{"id":2081},[13,4061,2085],{},[13,4063,2088,4064,2092],{},[16,4065,2091],{},[13,4067,2095],{},[83,4069,4070,4074,4078,4082],{},[86,4071,4072,2103],{},[16,4073,2102],{},[86,4075,4076,2109],{},[16,4077,2108],{},[86,4079,4080,2115],{},[16,4081,2114],{},[86,4083,4084,2121],{},[16,4085,2120],{},[13,4087,2124,4088],{},[16,4089,2127],{},[1587,4091,2131],{"id":2130},[13,4093,2134],{},[13,4095,4096],{},[16,4097,2139],{},[13,4099,2142],{},[83,4101,4102,4106,4110],{},[86,4103,4104,2150],{},[16,4105,2149],{},[86,4107,4108,2156],{},[16,4109,2155],{},[86,4111,4112,2162],{},[16,4113,2161],{},[13,4115,2165],{},[1587,4117,2169],{"id":2168},[13,4119,2172],{},[13,4121,2175],{},[83,4123,4124,4128,4132],{},[86,4125,4126],{},[16,4127,2182],{},[86,4129,4130],{},[16,4131,2187],{},[86,4133,4134],{},[16,4135,2192],{},[13,4137,2195],{},[54,4139,2199],{"id":2198},[13,4141,2202],{},[1854,4143,4144,4148,4152],{},[86,4145,4146,2210],{},[16,4147,2209],{},[86,4149,4150,2216],{},[16,4151,2215],{},[86,4153,4154,2222],{},[16,4155,2221],{},[54,4157,2225],{"id":2225},[13,4159,2228],{},[13,4161,2231,4162,2235],{},[16,4163,2234],{},[13,4165,2238],{},[51,4167],{},[13,4169,4170],{},[47,4171,2245,4172,899],{},[1899,4173,2248],{"href":1901},{"title":71,"searchDepth":253,"depth":253,"links":4175},[4176,4177,4182,4183,4188,4189,4190,4191,4196,4197,4202,4203],{"id":1564,"depth":253,"text":1565},{"id":1581,"depth":253,"text":1582,"children":4178},[4179,4180,4181],{"id":1589,"depth":264,"text":1590},{"id":1657,"depth":264,"text":1658},{"id":1681,"depth":264,"text":1682},{"id":1688,"depth":253,"text":1689},{"id":1727,"depth":253,"text":1728,"children":4184},[4185,4186,4187],{"id":1731,"depth":264,"text":1732},{"id":1780,"depth":264,"text":1781},{"id":1818,"depth":264,"text":1819},{"id":1848,"depth":253,"text":1849},{"id":1876,"depth":253,"text":1877},{"id":1927,"depth":253,"text":1928},{"id":1944,"depth":253,"text":1945,"children":4192},[4193,4194,4195],{"id":1951,"depth":264,"text":1952},{"id":2012,"depth":264,"text":2013},{"id":2034,"depth":264,"text":2035},{"id":2041,"depth":253,"text":2042},{"id":2077,"depth":253,"text":2078,"children":4198},[4199,4200,4201],{"id":2081,"depth":264,"text":2082},{"id":2130,"depth":264,"text":2131},{"id":2168,"depth":264,"text":2169},{"id":2198,"depth":253,"text":2199},{"id":2225,"depth":253,"text":2225},{},{"title":1538,"description":2280},[2286,2287,2288,2289,2290],{"id":4208,"title":4209,"body":4210,"category":1513,"cover":4687,"date":4688,"description":4689,"draft":1517,"extension":1518,"meta":4690,"navigation":1520,"path":4691,"readingTime":409,"seo":4692,"stem":4693,"tags":4694,"__hash__":4698},"blogs\u002Fblogs\u002Fnuxt-dotnet-jwt-auth.md","Nuxt 3 与 .NET 8 的 JWT 认证实践",{"type":7,"value":4211,"toc":4680},[4212,4216,4219,4222,4242,4246,4249,4332,4336,4347,4643,4647,4653,4656,4671,4674,4677],[54,4213,4215],{"id":4214},"为什么选-jwt","为什么选 JWT",[13,4217,4218],{},"在前后端分离的项目里，JWT 是最常见的认证方案之一。它把用户身份编码到一个自包含的 token 里，服务器无需维护 session 状态，天然适合分布式部署。",[13,4220,4221],{},"但 JWT 不是银弹。它有三个必须正视的问题：",[1854,4223,4224,4230,4236],{},[86,4225,4226,4229],{},[16,4227,4228],{},"token 一旦签发无法吊销","，除非维护 blacklist",[86,4231,4232,4235],{},[16,4233,4234],{},"payload 可被解码","（base64 不是加密），不能放敏感信息",[86,4237,4238,4241],{},[16,4239,4240],{},"过期时间权衡","：短则频繁登录，长则安全风险",[54,4243,4245],{"id":4244},"后端net-8-配置","后端：.NET 8 配置",[13,4247,4248],{},"在 Program.cs 里加入 JWT 认证：",[62,4250,4254],{"className":4251,"code":4252,"language":4253,"meta":71,"style":71},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(options =>\n    {\n        options.TokenValidationParameters = new TokenValidationParameters\n        {\n            ValidateIssuer = true,\n            ValidateAudience = true,\n            ValidateLifetime = true,\n            ValidateIssuerSigningKey = true,\n            ValidIssuer = builder.Configuration[\"Jwt:Issuer\"],\n            ValidAudience = builder.Configuration[\"Jwt:Audience\"],\n            IssuerSigningKey = new SymmetricSecurityKey(\n                Encoding.UTF8.GetBytes(builder.Configuration[\"Jwt:Key\"]))\n        };\n    });\n","csharp",[69,4255,4256,4261,4266,4271,4276,4281,4286,4291,4296,4301,4306,4311,4316,4321,4326],{"__ignoreMap":71},[236,4257,4258],{"class":238,"line":239},[236,4259,4260],{},"builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n",[236,4262,4263],{"class":238,"line":253},[236,4264,4265],{},"    .AddJwtBearer(options =>\n",[236,4267,4268],{"class":238,"line":264},[236,4269,4270],{},"    {\n",[236,4272,4273],{"class":238,"line":279},[236,4274,4275],{},"        options.TokenValidationParameters = new TokenValidationParameters\n",[236,4277,4278],{"class":238,"line":290},[236,4279,4280],{},"        {\n",[236,4282,4283],{"class":238,"line":313},[236,4284,4285],{},"            ValidateIssuer = true,\n",[236,4287,4288],{"class":238,"line":322},[236,4289,4290],{},"            ValidateAudience = true,\n",[236,4292,4293],{"class":238,"line":354},[236,4294,4295],{},"            ValidateLifetime = true,\n",[236,4297,4298],{"class":238,"line":384},[236,4299,4300],{},"            ValidateIssuerSigningKey = true,\n",[236,4302,4303],{"class":238,"line":392},[236,4304,4305],{},"            ValidIssuer = builder.Configuration[\"Jwt:Issuer\"],\n",[236,4307,4308],{"class":238,"line":401},[236,4309,4310],{},"            ValidAudience = builder.Configuration[\"Jwt:Audience\"],\n",[236,4312,4313],{"class":238,"line":409},[236,4314,4315],{},"            IssuerSigningKey = new SymmetricSecurityKey(\n",[236,4317,4318],{"class":238,"line":417},[236,4319,4320],{},"                Encoding.UTF8.GetBytes(builder.Configuration[\"Jwt:Key\"]))\n",[236,4322,4323],{"class":238,"line":431},[236,4324,4325],{},"        };\n",[236,4327,4329],{"class":238,"line":4328},15,[236,4330,4331],{},"    });\n",[54,4333,4335],{"id":4334},"前端nuxt-3-拦截器","前端：Nuxt 3 拦截器",[13,4337,4338,4339,4342,4343,4346],{},"在 Nuxt 里用 ",[69,4340,4341],{},"useFetch"," 的 ",[69,4344,4345],{},"onRequest"," 钩子统一注入 token：",[62,4348,4352],{"className":4349,"code":4350,"language":4351,"meta":71,"style":71},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export const useApi = () => {\n  const token = useCookie('auth-token')\n\n  return $fetch.create({\n    baseURL: useRuntimeConfig().public.apiBase,\n    onRequest({ options }) {\n      if (token.value) {\n        options.headers = {\n          ...options.headers,\n          Authorization: `Bearer ${token.value}`\n        }\n      }\n    },\n    onResponseError({ response }) {\n      if (response.status === 401) {\n        navigateTo('\u002Flogin')\n      }\n    }\n  })\n}\n","typescript",[69,4353,4354,4379,4408,4413,4431,4457,4474,4495,4509,4523,4548,4553,4558,4563,4577,4601,4618,4623,4629,4637],{"__ignoreMap":71},[236,4355,4356,4360,4364,4367,4370,4373,4376],{"class":238,"line":239},[236,4357,4359],{"class":4358},"s7zQu","export",[236,4361,4363],{"class":4362},"spNyl"," const",[236,4365,4366],{"class":422}," useApi ",[236,4368,4369],{"class":246},"=",[236,4371,4372],{"class":246}," ()",[236,4374,4375],{"class":4362}," =>",[236,4377,4378],{"class":246}," {\n",[236,4380,4381,4384,4387,4390,4394,4397,4400,4403,4405],{"class":238,"line":253},[236,4382,4383],{"class":4362},"  const",[236,4385,4386],{"class":422}," token",[236,4388,4389],{"class":246}," =",[236,4391,4393],{"class":4392},"s2Zo4"," useCookie",[236,4395,4396],{"class":242},"(",[236,4398,4399],{"class":246},"'",[236,4401,4402],{"class":249},"auth-token",[236,4404,4399],{"class":246},[236,4406,4407],{"class":242},")\n",[236,4409,4410],{"class":238,"line":264},[236,4411,4412],{"emptyLinePlaceholder":1520},"\n",[236,4414,4415,4418,4421,4423,4426,4428],{"class":238,"line":279},[236,4416,4417],{"class":4358},"  return",[236,4419,4420],{"class":422}," $fetch",[236,4422,133],{"class":246},[236,4424,4425],{"class":4392},"create",[236,4427,4396],{"class":242},[236,4429,4430],{"class":246},"{\n",[236,4432,4433,4436,4438,4441,4444,4446,4449,4451,4454],{"class":238,"line":290},[236,4434,4435],{"class":242},"    baseURL",[236,4437,227],{"class":246},[236,4439,4440],{"class":4392}," useRuntimeConfig",[236,4442,4443],{"class":242},"()",[236,4445,133],{"class":246},[236,4447,4448],{"class":422},"public",[236,4450,133],{"class":246},[236,4452,4453],{"class":422},"apiBase",[236,4455,4456],{"class":246},",\n",[236,4458,4459,4462,4465,4469,4472],{"class":238,"line":313},[236,4460,4461],{"class":242},"    onRequest",[236,4463,4464],{"class":246},"({",[236,4466,4468],{"class":4467},"sHdIc"," options",[236,4470,4471],{"class":246}," })",[236,4473,4378],{"class":246},[236,4475,4476,4479,4482,4485,4487,4490,4493],{"class":238,"line":322},[236,4477,4478],{"class":4358},"      if",[236,4480,4481],{"class":242}," (",[236,4483,4484],{"class":422},"token",[236,4486,133],{"class":246},[236,4488,4489],{"class":422},"value",[236,4491,4492],{"class":242},") ",[236,4494,4430],{"class":246},[236,4496,4497,4500,4502,4505,4507],{"class":238,"line":354},[236,4498,4499],{"class":422},"        options",[236,4501,133],{"class":246},[236,4503,4504],{"class":422},"headers",[236,4506,4389],{"class":246},[236,4508,4378],{"class":246},[236,4510,4511,4514,4517,4519,4521],{"class":238,"line":384},[236,4512,4513],{"class":246},"          ...",[236,4515,4516],{"class":422},"options",[236,4518,133],{"class":246},[236,4520,4504],{"class":422},[236,4522,4456],{"class":246},[236,4524,4525,4528,4530,4533,4536,4539,4541,4543,4545],{"class":238,"line":392},[236,4526,4527],{"class":242},"          Authorization",[236,4529,227],{"class":246},[236,4531,4532],{"class":246}," `",[236,4534,4535],{"class":249},"Bearer ",[236,4537,4538],{"class":246},"${",[236,4540,4484],{"class":422},[236,4542,133],{"class":246},[236,4544,4489],{"class":422},[236,4546,4547],{"class":246},"}`\n",[236,4549,4550],{"class":238,"line":401},[236,4551,4552],{"class":246},"        }\n",[236,4554,4555],{"class":238,"line":409},[236,4556,4557],{"class":246},"      }\n",[236,4559,4560],{"class":238,"line":417},[236,4561,4562],{"class":246},"    },\n",[236,4564,4565,4568,4570,4573,4575],{"class":238,"line":431},[236,4566,4567],{"class":242},"    onResponseError",[236,4569,4464],{"class":246},[236,4571,4572],{"class":4467}," response",[236,4574,4471],{"class":246},[236,4576,4378],{"class":246},[236,4578,4579,4581,4583,4586,4588,4590,4593,4597,4599],{"class":238,"line":4328},[236,4580,4478],{"class":4358},[236,4582,4481],{"class":242},[236,4584,4585],{"class":422},"response",[236,4587,133],{"class":246},[236,4589,267],{"class":422},[236,4591,4592],{"class":246}," ===",[236,4594,4596],{"class":4595},"sbssI"," 401",[236,4598,4492],{"class":242},[236,4600,4430],{"class":246},[236,4602,4604,4607,4609,4611,4614,4616],{"class":238,"line":4603},16,[236,4605,4606],{"class":4392},"        navigateTo",[236,4608,4396],{"class":242},[236,4610,4399],{"class":246},[236,4612,4613],{"class":249},"\u002Flogin",[236,4615,4399],{"class":246},[236,4617,4407],{"class":242},[236,4619,4621],{"class":238,"line":4620},17,[236,4622,4557],{"class":246},[236,4624,4626],{"class":238,"line":4625},18,[236,4627,4628],{"class":246},"    }\n",[236,4630,4632,4635],{"class":238,"line":4631},19,[236,4633,4634],{"class":246},"  }",[236,4636,4407],{"class":242},[236,4638,4640],{"class":238,"line":4639},20,[236,4641,4642],{"class":246},"}\n",[54,4644,4646],{"id":4645},"刷新-token-的陷阱","刷新 token 的陷阱",[13,4648,4649,4650,899],{},"很多教程只讲 access token，不讲 refresh token。实际生产环境里，",[16,4651,4652],{},"没有 refresh token 的 JWT 方案是不完整的",[13,4654,4655],{},"关键设计：",[83,4657,4658,4661,4668],{},[86,4659,4660],{},"access token：15 分钟有效期，放在内存或 cookie",[86,4662,4663,4664,4667],{},"refresh token：7 天有效期，",[16,4665,4666],{},"必须"," HttpOnly cookie，防止 XSS 窃取",[86,4669,4670],{},"刷新接口：服务端校验 refresh token，签发新的 access token",[54,4672,4673],{"id":4673},"总结",[13,4675,4676],{},"JWT 认证看似简单，但实际落地有不少细节。希望这篇文章能帮你避开常见的坑。",[1488,4678,4679],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":71,"searchDepth":253,"depth":253,"links":4681},[4682,4683,4684,4685,4686],{"id":4214,"depth":253,"text":4215},{"id":4244,"depth":253,"text":4245},{"id":4334,"depth":253,"text":4335},{"id":4645,"depth":253,"text":4646},{"id":4673,"depth":253,"text":4673},"\u002Fimages\u002Ftravel\u002Fhome-placeholder-1.jpg","2026-04-10","从前端登录到后端校验，一套可落地的最佳实践，包括刷新 token、跨域 cookie 和前后端错误处理的细节。",{},"\u002Fblogs\u002Fnuxt-dotnet-jwt-auth",{"title":4209,"description":4689},"blogs\u002Fnuxt-dotnet-jwt-auth",[4695,4696,4697],"Nuxt",".NET","Auth","zTBCAyICqoS0EMy1UunGXyWApv3FEjl9Jpg1kjGcs6Y",1786331541189]