游戏开发这个圈子里学习资料从来不是稀缺品稀缺的是我现在要用的时候能立刻找到它这件事。我自己做过几年独立项目也带过几个刚入行的新人收藏夹里躺着上千条链接硬盘里散着三个版本的教程压缩包笔记软件里还有一堆没头没尾的截图。真正要动手做一个原型的时候光是把这些东西翻出来对齐半小时就过去了。后来我干脆把这些东西全部收进一个仓库做成一份自己的游戏开发资源大全一个仓库搞定所有学习资料按方向分目录、按统一字段写元数据、用脚本自动体检死链新人拉下来十分钟就知道该看什么。这套东西跑了大半年最大的变化不是资料变多了而是找东西的时间从十几分钟压到几十秒。这篇东西讲的就是这个仓库怎么设计、怎么搭、怎么维护。它不是一份资源推荐清单而是一套收纳资料的工程方法目录怎么分层、每条资源要记哪些字段、命名怎么定、死链怎么自动扫、大文件怎么不把仓库撑爆、半年后怎么保证它还没烂掉。适合三类人看正在自学、资料已经乱成一锅粥的小团队里负责带新人、需要一份统一入口的以及单纯喜欢把知识体系整理成可检索结构的强迫症患者。不需要你会写复杂脚本会几条基本命令、能看懂最简单的 Python就足够把这套东西落地。1. 为什么我决定用一个仓库来管所有学习资料1.1 收藏夹式学习的三笔隐形账大部分人整理资料的方式是收藏即拥有。看到一篇讲碰撞检测的文章点个收藏刷到一个讲着色器的视频存进播放列表下载了一个素材包扔进待整理文件夹。当时的心理感受是我把它抓住了但实际发生的是一次延迟支付的负债。第一笔账是上下文丢失。半年后你翻到一条链接标题写着《空间划分入门》你已经想不起来当初为什么要存它——是为了优化碰撞检测还是为了解决场景剔除没有一条备注把它和你的具体问题绑在一起这条资源就等于失效了。第二笔账是链接腐烂。个人博客的平均存活周期远比想象中短我统计过自己早期收藏的一批技术博客两年后能正常打开的不到六成。第三笔账是无法协作。你可以把收藏夹分享给别人但那只是一串链接没有任何筛选信息和难度标注对方拿到手等于拿到一堆噪音。A 同学是我带过的一个转行自学者他的做法很典型把教程视频按第几周编号存进网盘然后用一个表格记录进度。三个月后他想回头查角色控制器怎么做发现自己当初把这块内容拆在了四个不同的视频里而表格里只写了第三周看完。这三笔账加在一起最直接的后果是重复劳动——同一类知识你会学两到三遍每次都是从零开始找。1.2 仓库、网盘、笔记软件到底差在哪我不认为仓库是万能的它只是在我的使用场景里恰好合适。把几种常见载体摆在一起对比区别会非常清楚。载体全文检索版本历史多人协作离线可用大文件维护成本浏览器收藏夹弱只有标题无无部分不支持极低但会烂本地文件夹依赖系统搜索无靠拷贝是支持中网盘同步盘弱有限一般需缓存支持中笔记软件强有强视产品弱中高版本仓库强纯文本完整强是需额外处理前期高后期低关键差异在版本历史和纯文本可检索这两条。仓库里的每条资源都是一行 Markdown 或者一行结构化数据这意味着你可以用 grep 做全文搜索可以 diff 出这周新增了什么可以回滚到上个月的状态也可以让别人的提交通过合并请求进来。笔记软件也能做到其中一部分但它的数据格式通常是私有结构导出一次要掉一层皮而且很难做提交即校验这种自动化。网盘的问题不在能力而在心智。网盘里没有这条资源已被验证可用的概念你永远不知道那个 2019 年的压缩包解压出来还能不能用。仓库里我可以加一个字段叫最后验证日期用脚本定期扫描过期的高亮出来。这个机制听起来很小但它把资料整理从一次性动作变成了持续运营。1.3 这套结构适合谁不适合谁先说适合的。第一类是正在自学的个人尤其是从别的行业转过来的你的知识是碎片化拼起来的特别需要一张自己的地图。第二类是三人以内的小团队没有专门的文档流程一份共享仓库就是最低成本的团队知识库。第三类是带新人的角色你不需要每次从零讲一遍渲染管线怎么理解直接给一个目录让他按顺序看。不适合的也很明确。如果你目前的阶段是先做完一个能跑的小 Demo那我建议你先别整理先做完——整理资料这件事本身有很强的拖延属性很容易变成我在为学习做准备的自我安慰。另外如果你的资料主体是几十 GB 的视频课程仓库不是好的托管方案仓库只该存索引视频本身放别处用链接和校验值引用。还有一类是已经有一套成熟笔记体系的人硬迁移过来的收益不大你可以只借鉴元数据字段设计那部分。注意这套东西的第一原则是索引优先于存储。仓库负责回答东西在哪、值不值得看、什么时候验证过不负责当硬盘。搞混这一点仓库会迅速膨胀成一个没人愿意克隆的怪物。2. 仓库的整体骨架设计2.1 顶层目录按产出物分不按资料类型分这是我踩过最大的一个坑。最早我的目录是这样的视频/、文章/、PDF/、代码/、素材/。看起来很整洁实际用起来极其痛苦因为你从来不会带着我要找一个 PDF的念头去检索。你的念头是我要做一个能跳能爬的角色或者我要让水面反射看起来不那么假。按载体分类等于按文件的物理属性分类和你的检索意图完全正交。改成按产出物分之后一切顺了。现在的顶层结构大概是这样game-dev-vault/ ├── 00-index/ # 总索引、标签表、阅读路线 ├── 10-engine/ # 引擎与语言上手路径、API 速查、项目模板 ├── 20-math-graphics/ # 数学地基、渲染管线、着色器 ├── 30-gameplay/ # 玩法、关卡、数值、手感调参 ├── 40-art-audio/ # 美术流程、素材授权、音频处理 ├── 50-engineering/ # 版本管理、构建、性能剖析、崩溃上报 ├── 60-release/ # 打包、多平台、商店页素材规格 ├── 70-cases/ # 案例拆解、复盘笔记 ├── 90-toolbox/ # 脚本、模板、配置文件 └── README.md数字前缀是我特意加的。文件管理器默认按字典序排列不加前缀的话art会排在engine前面和逻辑顺序对不上。用两位数或者三位数编号还有一个隐性好处中间留了空位。你以后想在20和30之间插一个物理与动画可以用25不用把后面全部重命名。这个技巧我是从印刷排版的行距里学来的很土但很管用。每一层内部我建议再分两层_links/放纯链接和元数据_notes/放我自己的理解和笔记。分开的原因很实际——链接是可能失效的、可以批量替换的笔记是原创的、不能丢的。混在一起的时候做一次全量链接迁移会把笔记也搅进去。2.2 每条资源的统一字段控制在 11 个以内字段设计是整个仓库的灵魂。字段太少检索不了字段太多填写成本高到你坚持不下去。我前后改了三版最后稳定在 11 个字段填一条大约 40 秒这个成本是可以长期承受的。字段是否必填作用填写建议id是唯一标识用于交叉引用方向缩写序号如gp-0012title是标题用于快速扫读保留原文标题别自己改写topic是二级分类从固定词表里选别自由发挥type是载体类型文章/视频/书/代码/工具/课程level是难度入门/进阶/专题三档足够lang是语言只标非母语减少填写量time否预计耗时视频必填文章可空url是主链接用短链或原始链接别用带追踪参数的mirror否备用位置存档地址或镜像说明license否授权素材类必填其余可空verified是最后验证日期格式统一YYYY-MM-DDtopic用固定词表这条我要单独强调。人性天然喜欢自由发挥第一次整理的时候你会写出渲染图形学画面表现三个其实是一个意思的标签两个月后你自己都搜不全。做法很简单在00-index/topics.md里维护一份允许值的清单脚本校验时发现不在清单里的直接报错。这个约束前三天会觉得烦一周后你会感谢自己。verified字段是这套体系里投入产出比最高的一个。它让你可以问出哪些资源已经两年没验证过了这种问题然后批量处理。我一般把超过 18 个月的标记为待复核超过 30 个月的直接降级到归档区。2.3 命名与路径的几条硬规矩路径规范看着是小事但凡涉及到脚本处理就会变成大事。我定了几条不留余地的规矩。目录名和文件名全部用小写英文加短横线不用中文、不用空格、不用下划线。原因有三个跨平台时中文路径在某些压缩工具里会乱码空格在 shell 脚本里需要额外转义写脚本时容易漏下划线和小写字母在等宽字体下容易看混短横线更清晰。这条在你自己电脑上可能无所谓但只要你想在自动化流程里跑一遍遍历就知道有多省事。资源条目的文件名我采用id-简写.md的形式比如gp-0012-coyote-time.md。带 id 是为了和索引表对应带简写是为了在文件列表里能看懂。同一目录下条目不要超过 60 个超过就再分子目录——这不是技术限制是人的浏览习惯一屏能扫完的量大概是 20 到 30 条再多就需要二次筛选。还有一条是链接要去掉追踪参数。你从搜索结果里复制出来的地址尾巴上经常挂着一串?utm_source...ref...。这些参数会让同一个资源在去重时被识别成两条也会在你分享给别人时暴露来源。写个小函数在提交前统一清洗成本很低。3. 资源分类与选型每个方向该收什么、不该收什么3.1 引擎与语言入口先定一个其余只做索引新手最常犯的错是同时开两个引擎。我见过太多人一边看 C# 那套商业引擎的教程一边又去试 C 那套的重型引擎三个月后两个都停在能创建项目、能放个方块。引擎本质上是工具链的组合你学的是它背后的编辑器工作流、资源管线、脚本生命周期这些东西在不同引擎之间是要重新建立的肌肉记忆并行学习等于把建立肌肉记忆的次数翻倍。选引擎我给一个粗糙但有效的决策顺序。第一步看目标平台和包体要求要做小体积的移动端或者网页端重型引擎直接排除。第二步看视觉目标写实风格的三维项目重型引擎的现成管线能省你半年二维或者风格化的轻量引擎反而更顺手。第三步看你现有语言基础熟悉 C# 就走对应的路线熟悉 C 就走另一条熟悉 JavaScript 就直接从网页游戏框架起步。第四步才看社区和教程数量——这一条重要但不该排前面因为教程多的引擎你不一定用得上。仓库里我是这么处理的10-engine/下面按引擎各建一个子目录但只有一个标_primary其余标_reference。_primary目录里放完整的上手路线、常用 API 速查、项目模板_reference目录只放迁移时需要注意的差异点和这个引擎的独有特性。这个标记不是永久的做新项目时可以换但同一时间只能有一个 primary。场景引擎形态该重点收集的资料二维小体量、快速出原型轻量开源引擎或网页框架场景组织、精灵动画、输入处理三维写实、中大型项目重型商业引擎材质系统、光照、地形、优化极致包体、自定义渲染底层图形接口 自研图形接口文档、内存管理、构建系统上架小游戏平台平台官方工具链平台 SDK、广告与支付接入、审核规范收集资料的时候有个反直觉的建议少收从零做一个完整游戏的长系列教程多收某个具体问题怎么解决的专题文章。长系列看着有安全感但它最大的问题是和你自己的项目不同步你跟着做完一个示例换到自己的题材上依然不会。专题文章短、针对性强遇到问题时能立刻用上。3.2 数学与图形学最容易囤积、最难消化的部分这个方向是重灾区。几乎每个人的仓库里都躺着几本图形学教材的 PDF 和一堆收藏了没看的线性代数课程。原因是这类知识看起来很重要但没有即时反馈你收藏它的那一刻获得的是安全感不是能力。我的处理办法是把这个方向拆成必须能徒手推和知道去哪查两类。必须能徒手推的其实不多向量加减与点乘叉乘的几何意义、矩阵乘法的顺序含义、坐标系变换的链路模型空间到世界空间到观察空间到裁剪空间、四元数为什么能避免万向锁。其余的东西比如具体某个光照模型的 BRDF 公式、某种空间加速结构的构建细节都是查得到就行。四元数这块我用一个生活类比讲给新人听效果不错想象你手里拿着一个手机做全景拍摄俯仰、偏航、翻滚三个轴分别转当俯仰转到 90 度时偏航和翻滚在物理上变成了同一个动作你丢掉了一个自由度——这就是万向锁。用三个角度存姿态本质上是用三个数字去描述一个三维旋转但三维旋转的自由度只有三个而参数空间是环形的某些点上必然退化。四元数用四个数加一个约束条件来描述避开了这个奇点。这个解释不严谨但足够让人明白为什么需要它剩下的严谨推导可以等真正用到时再看。渲染管线这部分仓库里我建议只放两类资料一张清晰的管线阶段示意图自己画的更好和一份每个阶段能做什么、不能做什么的对照。很多新手的问题是不知道某个效果该在哪一步做比如透明混合为什么必须在最后深度测试为什么在片元阶段之前。这类位置感比记住 API 有用得多。3.3 玩法与手感最容易被当成玄学其实全是参数玩法设计里最能量化的部分是手感game feel。这部分我强烈建议在仓库里单独建一个文件专门记录你调过的参数因为它是唯一一类你调完就忘、下次还得重调的东西。拿最经典的平台跳跃举例。假设你的角色起跳初速度是 12 米每秒重力加速度是 30 米每二次方秒那么跳跃高度和滞空时间可以直接算出来起跳高度h v² / (2g) 144 / 60 2.4米上升到顶点的时间t_up v / g 12 / 30 0.4秒完整滞空时间t_total 2 × 0.4 0.8秒如果玩家反馈跳得太飘、像月球漫步把重力改成 60速度提到 17此时高度h 289 / 120 ≈ 2.41米几乎不变但滞空时间变成2 × 17 / 60 ≈ 0.57秒。同样的跳跃高度通过提高重力和初速度可以让手感变脆。这个结论用文字描述很难传达但用两个数字一算就非常清楚这就是为什么参数要记进仓库。除了这两个基础量还有几个隐形参数是商业手感的秘密我全部记在30-gameplay/feel-params.md里参数典型值作用土狼时间0.08 ~ 0.15 秒离开平台后仍允许起跳的宽限输入缓冲0.10 ~ 0.20 秒落地前按下跳跃仍然生效可变跳跃高度松键后重力 ×2 ~ ×3短按小跳、长按大跳转向加速度0.05 ~ 0.12 秒到满速控制起步和急停的重量感顶点悬停顶点附近重力 ×0.5让滞空更有漂浮节奏这些数值不是标准答案是起点。不同类型游戏差得很远硬核动作和休闲平台跳跃的土狼时间能差三倍。但有了起点你调整的时候就不是瞎试而是在一个有坐标的空间里移动。仓库里我会为每个项目单独存一份参数快照附一句这个手感对应的是什么类型下次开新项目可以直接拿来当基线。3.4 美术与音频只收能立刻上手的重点盯授权美术和音频方向的资料我的筛选标准比技术方向严得多因为这一类东西的看起来有用但用不上比例极高。一个两小时的厚涂教学视频对你做一个二维小体量项目几乎零帮助。所以我只收三类能在半小时内产出可用素材的流程、素材资源库、授权说明。二维方向我建议先啃像素画和矢量剪影这两条线它们对基本功要求低、出图快、风格容错高。三维方向先学低模加手绘贴图别一上来就碰写实材质。动画方面二维骨骼动画和逐帧动画的取舍很实际逐帧表现力强但成本高骨骼动画修改方便但容易僵。这个选择要在项目开始前定中途换代价很大。音频这块很多人直接忽略其实是性价比最高的一环。一个游戏的音效做得对玩家感受到的完成度会明显上升。仓库里我分了两个目录sfx/放音效素材和来源music/放音乐和授权。音频中间件这类工具只在项目需要动态混音、实时参数控制的时候才收小项目直接用引擎内置的音频源就够了。授权是必须单独说的。素材的授权类型直接决定你能不能用我把常见的几类做成了一张常驻对照表授权类型能否商用是否需署名能否修改常见坑完全放弃权利可以不需要可以少数要求不得暗示作者背书署名型可以必须可以署名格式和位置有要求相同方式共享可以必须可以但衍生作品需同协议商业项目里很难满足仅非商业不可以视条款视条款广告变现也算商业自定义授权逐条读逐条读逐条读最容易踩雷的一类我的做法是每一条素材记录必须填license字段填不出来的一律不收。这个规则很粗暴但它避免了我见过的最惨的一种情况——项目做完准备上架发现核心美术素材的授权不允许商用重做来不及换素材要重画三个月。3.5 工程化与发布绝大多数教程清单都缺这段这是我最想强调的一节。你去搜游戏开发学习路线十份里有九份会告诉你学引擎、学编程、学美术几乎没有人告诉你版本管理怎么做、构建流水线怎么配、崩溃怎么上报、包体怎么压。但这些恰恰是从能跑到能发布之间最大的鸿沟。版本管理这块游戏项目和普通软件项目最大的区别是二进制资产多。一张贴图、一个模型、一段音频都是无法做行级 diff 的大文件。直接提交会让仓库体积迅速失控。我做过的计算是一个中等体量的三维项目原始资产加中间文件很容易到 8 到 15 GB。如果在 10 Mbps 的上行带宽下克隆理论最短时间是15 × 1024 × 8 / 10 ≈ 12288秒也就是三个多小时。这个体验足以劝退任何一个想参与的人。解决办法是用大文件版本管理扩展把二进制资产单独处理同时把可再生的中间文件排除掉。判断标准很简单这个文件能不能由别的文件重新生成能生成的一律不进版本库包括烘焙后的光照数据、导入产生的缓存、编译产物。这条规则能砍掉一半以上的体积。详细的配置我在 4.5 节给出来。构建和发布这块要收的资料包括各平台的打包参数含义、包体压缩的取舍纹理压缩格式、音频压缩格式、符号文件的保留方式、商店页的素材规格图标尺寸、截图比例、宣传片时长限制。最后这条特别容易漏我见过项目做完卡在商店页素材不合格上返工了整整一周。这些规格要求会变所以每一条都要带verified日期。4. 落地实操从零把这个仓库跑起来4.1 初始化与目录创建前面讲的都是设计现在动手。先建仓库、建目录一条命令搞定。mkdir -p game-dev-vault cd game-dev-vault git init mkdir -p 00-index for d in 10-engine 20-math-graphics 30-gameplay 40-art-audio \ 50-engineering 60-release 70-cases 90-toolbox; do mkdir -p $d/_links $d/_notes done touch 00-index/topics.md 00-index/reading-path.md touch README.md .gitignore .gitattributes顺手把.gitignore写好这一步比多数人想的更重要# 编译与缓存 build/ dist/ obj/ *.cache *.tmp # 大体积中间文件 *.fbx.autosave *.blend1 *.psd~ # 本地环境 .env .DS_Store Thumbs.db00-index/reading-path.md这个文件是用来放阅读路线的它的作用是把散落的资源串成有先后的路径。比如零基础到能做出一个可玩原型这条路线大概包含 8 到 12 条资源每条标注预计耗时。这个文件我建议手写别自动生成因为顺序本身就是你的判断而判断才是这个仓库最有价值的部分。4.2 索引表与条目模板每个方向目录下的_links/index.md是一个表格字段和 2.2 节对齐。为了让填写成本尽可能低我在90-toolbox/里放了一个条目模板新增资源时复制粘贴改几处就行。!-- 90-toolbox/templates/entry.md -- ### gp-0012 平台跳跃的土狼时间是怎么实现的 - topic: 手感调参 - type: 文章 - level: 进阶 - lang: 中文 - time: 15 分钟 - url: https://example.invalid/coyote-time - mirror: 本地存档 2025-03 快照 - license: 未标注 - verified: 2025-03-14 **为什么收它**用一个极小的示例说明了离开平台后仍可起跳的判定窗口 并给出了不同游戏类型下的取值区间。 **要点摘录** 1. 判定依据是离地后的帧计数不是距离。 2. 窗口过长会让玩家感觉能凭空二段跳过短则几乎无感。这里有个细节值得说为什么收它这一栏必须写。它是整个仓库里唯一无法被搜索引擎替代的东西因为它是你的判断。我给自己定的下限是三十个字少于三十个字说明我其实没想清楚为什么收那就先别收。执行了几个月之后这一栏成了我回看时最有价值的部分——很多条目的链接已经失效了但那句判断还在。00-index/topics.md里维护允许的分类词表格式很朴素一行一个# 允许的 topic 取值 - 引擎上手 - 脚本与生命周期 - 资源管线 - 线性代数 - 渲染管线 - 着色器 - 物理与碰撞 - 手感调参 - 关卡设计 - 数值与经济 - 二维美术 - 三维美术 - 动画 - 音效与音乐 - 版本管理 - 构建与打包 - 性能剖析 - 授权与合规4.3 元数据校验脚本把填错了挡在提交之前仓库条目一多手写的错误必然出现日期格式不统一、id 重复、topic 写了个词表外的词、链接少了协议头。这些错误肉眼很难全查但脚本一秒就能扫完。下面这个脚本只依赖标准库复制就能跑。#!/usr/bin/env python3 校验资源条目的元数据完整性与格式。 import re import sys from pathlib import Path ROOT Path(__file__).resolve().parent.parent INDEX_DIR ROOT / 00-index REQUIRED [topic, type, level, url, verified] DATE_RE re.compile(r^\d{4}-\d{2}-\d{2}$) FIELD_RE re.compile(r^-\s([a-z]):\s*(.)$) HEAD_RE re.compile(r^###\s([a-z]{2}-\d{4})\s(.)$) def load_topics(): path INDEX_DIR / topics.md if not path.exists(): return set() topics set() for line in path.read_text(encodingutf-8).splitlines(): line line.strip() if line.startswith(- ): topics.add(line[2:].strip()) return topics def parse_entry(path: Path): text path.read_text(encodingutf-8) lines text.splitlines() entry_id, title, fields None, None, {} for line in lines: m HEAD_RE.match(line) if m and entry_id is None: entry_id, title m.group(1), m.group(2) continue m FIELD_RE.match(line) if m: fields[m.group(1)] m.group(2).strip() return entry_id, title, fields def main(): topics load_topics() problems, seen_ids, count [], {}, 0 for path in sorted(ROOT.rglob(*.md)): if _notes in path.parts or path.name topics.md: continue if not re.match(r^[a-z]{2}-\d{4}-, path.name): continue count 1 entry_id, title, fields parse_entry(path) if not entry_id: problems.append(f{path}: 缺少 ### id 标题行) continue if entry_id in seen_ids: problems.append(f{path}: id 重复已存在于 {seen_ids[entry_id]}) seen_ids[entry_id] path for key in REQUIRED: if not fields.get(key): problems.append(f{path}: 缺少必填字段 {key}) if fields.get(verified) and not DATE_RE.match(fields[verified]): problems.append(f{path}: verified 日期格式应为 YYYY-MM-DD) url fields.get(url, ) if url and not url.startswith((http://, https://)): problems.append(f{path}: url 缺少协议头) topic fields.get(topic) if topic and topics and topic not in topics: problems.append(f{path}: topic {topic} 不在词表中) print(f扫描条目 {count} 条发现问题 {len(problems)} 个) for p in problems: print( -, p) return 1 if problems else 0 if __name__ __main__: sys.exit(main())这个脚本里有个设计选择值得解释我为什么用rglob全盘扫描而不是维护一份文件清单因为清单会过期。人手动维护的清单加文件时忘了更新校验就漏了。全盘扫的代价是慢一点但在这个体量下几千个文件完全不是问题。相反我特意给_notes目录开了豁免因为笔记是我自己的自由文本不该被元数据规范约束。4.4 死链检测并发数是要算的不是拍脑袋的这是维护环节最核心的自动化。没有它仓库会在半年内退化成一半是死链的遗迹。先算一笔账。假设仓库有 1500 条链接单条请求的平均耗时含 DNS、握手、响应按 0.6 秒估。串行跑完需要1500 × 0.6 900秒也就是 15 分钟。这个时长会超过大多数自动化环境的单任务时间上限而且失败重跑的成本很高。改成 16 个并发理论耗时约900 / 16 ≈ 56秒实际因为网络抖动大概 70 到 90 秒完全可以接受。那为什么不干脆开 200 个并发两个原因。一是多数站点的友好阈值在每秒个位数请求打太猛会被限流甚至临时封禁结果反而更慢。二是本地文件描述符和连接数有上限开太大会出现莫名的连接重置。16 到 32 是一个稳妥区间我最终定在 16。#!/usr/bin/env python3 并发检测资源链接可用性输出疑似死链清单。 import re import urllib.error import urllib.request from concurrent.futures import ThreadPoolExecutor from pathlib import Path ROOT Path(__file__).resolve().parent.parent URL_RE re.compile(r^-\surl:\s*(\S)$, re.M) TIMEOUT 8 WORKERS 16 UA Mozilla/5.0 (compatible; link-check/1.0) def collect_urls(): urls {} for path in ROOT.rglob(*.md): if _notes in path.parts: continue text path.read_text(encodingutf-8) for u in URL_RE.findall(text): urls.setdefault(u, []).append(str(path.relative_to(ROOT))) return urls def check(url): req urllib.request.Request(url, methodHEAD, headers{User-Agent: UA}) try: with urllib.request.urlopen(req, timeoutTIMEOUT) as resp: return url, resp.status, except urllib.error.HTTPError as e: # 有些站点不支持 HEAD回退到 GET if e.code in (403, 405, 501): try: req urllib.request.Request(url, headers{User-Agent: UA}) with urllib.request.urlopen(req, timeoutTIMEOUT) as resp: return url, resp.status, except Exception as e2: return url, None, repr(e2) return url, e.code, except Exception as e: return url, None, repr(e) def main(): urls collect_urls() print(f待检测链接 {len(urls)} 条并发 {WORKERS}) bad [] with ThreadPoolExecutor(max_workersWORKERS) as pool: for url, status, err in pool.map(check, urls): ok status is not None and 200 status 400 if not ok: bad.append((url, status, err, urls[url])) print(f疑似异常 {len(bad)} 条) for url, status, err, refs in bad: print(f\n[{status or ERR}] {url}) print(f 引用: {, .join(refs)}) if err: print(f 详情: {err}) if __name__ __main__: main()注意脚本报出来的异常不等于死链。403 通常是站点拒绝脚本访问429 是限流5xx 是对方服务器临时故障。这几种都要人工点进去确认一遍再决定是否删除我见过不止一次因为批量清理把好链接误删的情况。保守的处置策略是这样的第一次异常标记为待确认连续三次间隔一周都异常才降级到_archive/目录并且保留条目但注释掉链接而不是直接删。删除是不可逆的注释是可逆的。这个小习惯救过我不下五次。4.5 大文件与二进制资产的处理回到 3.5 节留下的问题怎么让仓库不至于膨胀到几个 GB。答案是两条规则加一份配置。第一条规则可再生的一律不进库。烘焙数据、导入缓存、编译产物、自动生成的缩略图全部写进.gitignore。第二条规则必须进库的二进制资产走大文件版本管理扩展配置写在.gitattributes里。# .gitattributes *.psd filterlfs difflfs mergelfs -text *.png filterlfs difflfs mergelfs -text *.jpg filterlfs difflfs mergelfs -text *.tga filterlfs difflfs mergelfs -text *.fbx filterlfs difflfs mergelfs -text *.blend filterlfs difflfs mergelfs -text *.wav filterlfs difflfs mergelfs -text *.mp3 filterlfs difflfs mergelfs -text *.mp4 filterlfs difflfs mergelfs -text即便如此我还是要强调一个克制原则这个仓库的定位是资料索引不是资产仓库。如果你要存的是自己项目的美术源文件那应该另开一个资产仓库和索引仓库分开。索引仓库的体积目标是纯文本、克隆在十秒以内。我目前的索引仓库大概 2000 多条资源纯文本加起来不到 1.5 MB克隆几乎是瞬间完成的。这个体验差异决定了别人愿不愿意参与进来。顺便说一下体积的估算方法方便你给自己设一条红线纯文本条目平均约 600 字节含标题、字段、摘要、要点2000 条就是2000 × 600 / 1024 ≈ 1172KB约 1.1 MB。加上索引文件和脚本控制在 3 MB 以内是完全可行的。超过 20 MB 就该停下来检查是不是混进了不该进库的东西。4.6 自动化提交即校验脚本写好了放在本地靠自觉去跑最终还是会被忘记。所以最后一步是把校验和死链检测挂到提交环节上。下面是一份通用的流水线配置具体路径按你所用的托管平台的约定调整。name: vault-check on: push: branches: [main] schedule: - cron: 0 3 * * 1 # 每周一凌晨跑一次全量死链检测 jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: 元数据校验 run: python 90-toolbox/scripts/validate.py - name: 死链检测仅定时任务执行 if: github.event_name schedule run: python 90-toolbox/scripts/check_links.py continue-on-error: true这里有三个细节我调过好几轮。第一死链检测只在定时任务里跑不在每次提交时跑。因为网络检测受对方站点影响把它挂在提交环节会让你的提交通道变得极不稳定一次对方站点超时就把你的正常提交挡了。第二加了continue-on-error让死链检测失败不阻塞主流程结果只作为报告。第三元数据校验是硬性阻塞的因为它是纯本地判断结果确定没有误报风险。5. 维护机制让仓库半年后还活着5.1 季度体检与三次未用即降级整理类项目几乎都死在同一件事上建的时候很兴奋一个月后不再打开。对抗这件事的办法不是靠意志力是靠机制。我给自己定了一个很轻的季度流程每次不超过一小时。第一步跑一遍元数据校验和死链检测把异常清单过一遍。第二步看verified超过 18 个月的条目挑十条抽检能打开且内容还有效的更新日期失效的处理掉。第三步做一次减法把过去三个月里引用次数为零、且我自己判断大概不会再用的条目移到_archive/。移动而不是删除因为人的需求会变。减法这一步我用一个粗暴的规则连续三次季度体检都没被引用、也没被打开的条目降级到归档区。这个规则的合理性在于资料的真正价值不取决于它本身多好而取决于它在你实际工作中的调用频率。一条讲得很透彻但你从没用过的文章占用的其实是你的注意力预算。提示给仓库加一个usage.log用极简格式记录日期 id 一句话用途比如2025-03-14 gp-0012 调跳跃手感翻了一下。写这一行只要五秒但它是你判断哪条资源真正有用的唯一客观依据。三个月后回头看你会惊讶地发现高频引用的可能只有二十来条。5.2 版本钉死与快照策略资料的有效性是有时效的。同一个地址今年讲的是某个引擎版本的工作流明年接口一改内容就过时了。应对方式有两种我两种都用。第一种是在条目里写清适用版本。比如适用于某引擎 2022 及之后的版本这句话很关键它让半年后的你知道该怎么判断这条资源还能不能信。第二种是对关键资源做快照。核心的 API 速查、版本迁移说明、授权条款我会保存一份纯文本快照放进_notes/snapshots/文件名带日期。快照的意义不是防死链而是防内容被悄悄改掉——有些文档会静默更新你按旧版写的代码突然对不上有了快照就能对照出差异在哪。快照的取舍也要算成本。一份纯文本快照大概几十 KB存一百份也就几 MB完全可以接受。但网页截图就没必要存了图片体积大、检索不了、还容易过期。我在仓库里只存两类快照文本内容和配置片段不存截图。5.3 版权与引用只做索引不做分发这一条必须说清楚。资源大全这个形态天然带着版权风险而风险几乎全部来自一个动作把别人的内容复制进自己的仓库。我的原则很简单只存链接、标题、自己的判断和摘要不存原文全文。摘要要短并且明确标注这是自己的理解不是原文摘录。素材类的资源license字段填不出来的一律不收。教程类资源如果原作者明确写了禁止转载那我就只存链接连摘要都写得极简。另一个容易忽略的点是引用格式。当你把别人的观点写进自己的笔记时至少保留来源 id这一条线索。回看的时候你可以顺着 id 找到原链接判断这个说法是否被断章取义了。这不是形式主义我确实遇到过自己几个月前的笔记因为当时摘得太随意回看时完全误解了原意的情况。6. 踩坑与排查实录6.1 常见问题速查表下面这些问题都是我在实际维护中真实遇到过的按出现频率排序。现象大概率原因排查动作处理方式校验脚本报大量缺字段模板改动后旧条目没同步看报错文件的创建时间批量补字段或对旧条目降级豁免死链检测耗时异常长并发被限流触发了退避重试看日志里的状态码分布并发降到 8加请求间隔仓库克隆很慢混入了大文件或历史提交里有大对象查目录体积和提交历史清理历史或迁移到独立资产库同一资源出现两条链接带不同追踪参数对比 url 去掉参数后的主体保留信息更全的一条另一条注释掉搜索结果太杂topic 词表被自由发挥污染统计 topic 取值分布合并同义项更新词表并重跑校验中文路径在某些工具里乱码目录或文件名含中文或空格用find列出非 ASCII 路径批量重命名脚本处理多人提交频繁冲突都改同一个 index 文件看冲突集中在哪些行拆分索引文件按方向分文件定时任务里检测结果不稳定对方站点响应波动对比多次运行结果三次异常才降级别立即删最后一条我要单独展开因为它是唯一一条看起来是技术问题、其实是流程问题的。早期我设的是检测失败即标记失效结果有一次对方站点维护了六个小时我一次批量运行把四十多条好链接全标成了死的。改成连续三次才降级之后这类误伤基本消失了。自动化越强越要给它留缓冲。6.2 只有踩过才知道的几个细节第一个细节先写阅读路线再往里加资源。我最初的做法是疯狂往里塞想着以后再整理结果三个月后仓库里有四百多条资源但没有一条路径我自己都不知道该从哪开始看。后来我改成反过来的顺序先定一条从零到能做出可玩原型的路线只写 12 条然后围绕这条路线去补资源。这个顺序的差别在于路线会自然淘汰掉大量不相关的资源你的收集效率反而更高。第二个细节把没看懂也记下来。我有个习惯遇到看不懂的内容就在条目的要点摘录里写一句这里没懂为什么深度预通道能减少过度绘制。这句话的价值远超我的预期因为它把模糊的困惑变成了一个具体的问题半年后我可能就在另一篇文章里找到了答案然后回来补一句已解决原因是……。仓库因此不只是一个书架还变成了一个待办清单。第三个细节别追求全。我见过太多人卡在还差一点就整理完了的状态里然后永远没开始用。资料整理本身是无限游戏永远有更好的资源。我的做法是给自己定一个够用即停的阈值每个方向有 8 到 15 条、覆盖入门和进阶两档、跑通一次元数据校验就算这一版完成直接开始用。用起来之后缺口会自己暴露出来那时候补的才是真正需要的。第四个细节把最难自证的部分外包给脚本。元数据格式、重复检测、死链、路径规范这些全是机器能判的别浪费注意力。人应该只做机器做不了的两件事判断这条资源值不值得收以及把资源串成有先后顺序的路径。认清这条分工仓库的维护成本会下降一个数量级这也是这套体系能持续半年以上的真正原因。如果非要说一句个人体会那就是这个仓库最大的产出不是那两千多条资源而是我在写为什么收它这一栏时被迫想清楚的那些东西。很多条资源在写完那一栏之后我就删了因为我发现自己根本说不出它对我的价值。这个筛选过程本身比仓库里的任何一条链接都值钱。
企业数字化 ERP 产品动态
相关推荐
NVIDIA GPU世代演进与规格选型指南:从P100到Blackwell 做AI基础设施这几年,我最大的体感是:GPU这玩意儿不能只看“型号数字大不大”。从P100到V100,再到A100、H100、H200和Blackwell,NVIDIA GPU的世代演进背后,是算力、显存、互联三条线的同时升级。而规格对比这件事&#… · 2026/11/4 3:45:31
Adobe XD 59.0 安装、首选项与原型交付实操指南 Adobe XD 59.0 这个版本号,最近问的人确实变多了。原因不复杂:一部分人手上压着几年前的设计源文件要打开、要改版;一部分人所在团队的协作惯性还在,交付流程没换;还有一部分是刚入门学 UI 的,被一堆新工具… · 2026/11/4 3:44:30
全国840站日照时数批量转日总太阳辐射Python实践 简介:面向气象、气候与太阳能资源评估场景的Python数据处理脚本,可将全国840个气象站点的日照时数观测值换算为日总太阳辐射,适合科研人员、数据分析爱好者及具备基础Python能力的学习者使用。压缩包内共3个文件,以2个CSV数据文件… · 2026/11/4 3:44:30
中小学信息学竞赛真题的CT式诊断与动态验证方法 简介:本资源为2024年海淀区中小学生信息学竞赛校级预选赛官方试题PDF,面向小学高年级及初中阶段编程初学者,聚焦基础编程能力与算法思维的阶段性测评。试题涵盖编程基础知识单选题(变量命名、赋值语句、进制转换、表达式运算、逻辑… · 2026/11/4 8:36:06
Spring Boot与微信小程序打造智能市容考核系统 1. 项目概述 这个基于Spring Boot和微信小程序的市容监察城市管理考核管理系统,是我最近完成的一个毕业设计项目。作为一名有10年开发经验的全栈工程师,我设计这个系统的初衷是想解决城市管理中常见的考核效率低下、数据不透明等问题。系统采用了前后端分… · 2026/11/4 8:36:06
GEO优化与AI推荐系统的地域内容策略 1. GEO概念解析:内容优化的地理维度 GEO(Geographic Targeting)是数字内容分发领域的核心策略之一,指根据用户地理位置特征进行内容优化的技术体系。这个看似简单的概念背后,其实包含着从数据采集到算法适配的完整技术… · 2026/11/4 8:36:06
ISO/IEC 42001:2023 是组织级 AI 治理的结构性约束协议 简介:本资源为国际标准化组织(ISO)与国际电工委员会(IEC)联合发布的首版人工智能管理体系标准——ISO/IEC 42001:2023官方英文原版PDF文档,面向AI产品研发团队、信息技术安全管理者、企业质量体系建设负责人… · 2026/11/4 8:36:06
.NET Core与微信小程序构建市容监管系统实践 1. 项目概述 市容监察管理系统是一个基于.NET框架和微信小程序的综合解决方案,旨在为城市管理部门提供高效、便捷的市容环境监管工具。作为一名从事软件开发十余年的工程师,我深知这类系统在实际应用中的痛点——传统纸质记录方式效率低下、信息传递滞后… · 2026/11/4 8:36:06
SequenceO1:当推理从token级跃升到序列级,长文本生成的新范式 开篇先说清楚一件事:这篇论文的标题挂着“SequenceO1”,但别被名字骗了。它讲的不是又一个“把O1的结果蒸馏成SFT数据”的跟风工作,而是把O1那套“先想后答”的推理范式,从“单步思考”推广到了“整段序列生成”的层面。我读第一遍… · 2026/11/4 8:35:06
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38