日照模拟器(日照助手)制作全流程
从零到上线的完整链路:引擎 → 数据 → UI → 构建 → 验收 → 部署 → 分发 → 运维。本技能来自一个已上线双形态工具(全功能版 + 三步向导版,共用同一引擎)的实战沉淀,所有铁律均踩过坑验证。
总原则(三条红线)
- 严禁编造数据:日照时数、建筑坐标、城市气候区一律来自真实数据或官方规范,决策前交叉验证。
- 引擎先行:先写纯数学引擎并通过沙箱验证,再碰 UI。日照计算 = 天文算法 + 几何求交,不依赖 GPU 阴影(Three.js 只做可视化演示,不参与计算)。
- 发布版必须脱敏:构建源里的地图 API Key、内部路径、私有数据严禁进入对外产物。
第 1 步:引擎先行
- 太阳位置:用天文算法库(如 SunCalc,纯 JS ~9KB)计算任意日期/时刻的太阳高度角、方位角。
- 日照时数计算:对每个受测楼层逐时刻做「遮挡判定」——从受测点向太阳方向做射线,与周边建筑体块(长方体/异形多边形拉伸)求交;被遮挡则该时刻不计入。全天(有效时段)累加即日照时数。这是纯数学,可在 Node 里跑,不依赖浏览器。
- 国标口径(必须按城市气候区自动定国标日,严禁写死单一日期):
- GB 50180-2018 表 4.0.9:Ⅰ/Ⅱ/Ⅲ/Ⅶ 气候区 = 大寒日(01-20)≥2h,有效时段 8–16 时;Ⅳ/Ⅴ/Ⅵ 区 = 冬至日(12-22)≥1h,有效时段 9–15 时。
- 气候区划据 GB 50178-93。例:上海 = Ⅲ 夏热冬冷区 → 默认大寒日。
- 两部规范有分歧的城市取严的一侧(如按 GB 50178-93 定区,不按更宽松的新表)。
- 联动规则:日期跟随城市(换城市→按该城气候区换国标日);用户手动改过日期/节气后解除联动,提供「恢复默认」按钮交还。跨月顺延方向不同(大寒在 1 月、冬至在 12 月),用
MM-DD补零后字符串比较统一处理,避免日期运算出错。
第 2 步:数据层离线烘焙
- 城市气候区表、小区/建筑坐标、楼栋数据等烘焙成 CommonJS 模块(
module.exports),便于 Node 复跑、多端同步与测试引用。 - 坐标来自权威来源(地图 API / 官方公示),逐条带来源与核验记录;坐标不能编。
- 坐标检索字段带拼音/别名,容错用户输入。
- ⚠️ 同一张表在多端各有一份时(如 H5 端与小程序端),必须同步更新并写同步脚本核对——两张气候区表不同步是真实踩过的坑。
第 3 步:UI 双形态
- 全功能版:地图定位 + 勾楼 + 楼栋编辑 + 热力图 + 3D 演示,面向深度用户。
- 三步向导版:选城市 → 选小区/描楼 → 出报告,面向小白用户。两个版本共用同一引擎模块,日照结果必须逐位一致。
- ES5 纪律:
var+function,不用箭头函数/let/const/模板字符串(兼容老 WebView/微信内置浏览器)。 - 画布交互铁律(只绑鼠标 = 手机上画不了楼,上线即废):
mousedown/mousemove/mouseup与touchstart/touchmove/touchend必须同时绑;addEventListener第三个参数写false,不要传{passive:false}对象(老 iOS Safari 会把它当 capture=true);touchend取changedTouches[0](此时touches已为空);touchcancel丢弃当前这一笔。
第 4 步:构建管线
- 写 Node 构建脚本(如
build_wizard.js):把 CommonJS 模块内联成自包含单文件(真源与产物分目录,防止同名覆盖)。 - 发布前混淆压缩(terser):需
NODE_PATH指向 workspace 的 node_modules(~/.workbuddy/binaries/node/workspace/node_modules),node 用 managed 版本绝对路径。 - 整树复制(
fs.cpSync recursive)时加filter排除手动维护目录——否则下次构建会静默覆盖掉手工替换的内容(真实事故:自动化每周构建把分享包版打回旧向导版)。filter 排除目录即排除整个子树。 - 产物目录只放最终可发布内容;含明文 Key 的构建源目录与产物目录物理分离。
第 5 步:验收三件套(缺一不可)
- 引擎逐位对比:用 Node
vm沙箱加载构建产物,抽取其内联引擎,与源码模块对同一批测试用例跑出结果逐位对比——同时证明「内联没破坏代码」+「计算正确」。 - DOM/交互冒烟:起浏览器或用 Node 最小 DOM 桩 eval 交互代码段,验证事件绑定与核心交互(比整页浏览器加载快且确定;用无头浏览器时注意 CDN 拖慢 load 导致超时,可只校验本地资源)。
- 线上 md5 验证:部署后拉线上文件与本地产物比对 md5,逐位确认部署生效。⚠️ Git Bash 下
curl -o /tmp/x不落盘,临时文件一律用当前目录相对路径。
第 6 步:部署
- 只部署产物目录(如
dist/、dist/子站/),绝不直接部署构建源目录。 - 部署目标标识通常由目录路径派生且跨会话稳定:同一路径重部署 = 复用同一站点 = 链接不变;要换某站点内容,把新内容放进它的部署目录再重部署同一路径。
- 分发版(zip/下载包):脱敏后单独打包,README/使用说明用 UTF-8 落盘(Windows 下注意源文件名可能是 GBK 乱码,读内容按 UTF-8,别按 GBK 解码正文)。
第 7 步:分发(飞书覆盖上传保链接)
- 已发布的下载链接要换版本时,用覆盖上传保 file_token:
lark-cli drive +upload --file <zip> --file-token <旧token> --name <新名>→ 链接不变,教程/文章里所有引用零改动。新建上传 = 新链接 = 要改所有引用文档,是下策。 - ⚠️ lark-cli 上传成功时可能无任何 stdout——静默成功≠失败,必须用
drive +inspect/ 元数据查询复核标题与 owner。
第 8 步:自动化运维
- 用定时自动化(如每周一/五):同步数据源 → 构建 → 部署 → md5 验证,形成闭环。
- 自动化 prompt 里写明红线:禁跑已废弃的构建脚本、禁部署手动维护目录、禁删构建 filter、部署目标清单与期望标识写死。自动化的输入数据路径、NODE_PATH、可执行文件路径全部用绝对路径。
- 每次部署后自动比对 md5(比对对象选代表性产物文件),不一致即报错,不静默通过。
参考文档
references/pitfalls.md— 全量踩坑清单(含每条的事故场景与修复方法),动手前通读一遍。