志睿源官网|产品开源仓库链接 上线记录
产品详情页新增「开源仓库」区块:已开源的 5 个产品均可一键跳转 GitCode / Gitee 仓库,源码公开可查;侧栏入口与主体区锚点同步上线。
时间:2026-09-17 | 范围:产品详情页「开源仓库」区块 状态:已上线,本地与生产双向验收通过
一、需求与决策
老板原话:对官网上产品介绍里,或其他栏目,有关于我们开源项目的,要把开源的 gitee、gitcode 链接放出来。
三个决策点已确认:
| 决策 | 选择 |
|---|---|
| 开源范围 | 已开源的 5 个产品 + web-spa-demo 挂到 Fun.js(文案里注明它不是 Fun.js 框架本体) |
| 展示位置 | 只挂产品详情页(侧栏区块 + 主体区入口) |
| 数据方式 | 后台结构化字段,改完即时生效 —— 与 CMS 的数据驱动口径一致,老板可自行维护 |
二、仓库与产品的对应关系
已开源账号下的仓库,与站上产品的对应关系如下(17 条地址全部实测 HTTP 200,零死链):
| 站点产品 | 仓库 | 双平台 |
|---|---|---|
| showtext.js | showtext | ✅ |
| nagent | nagent-remote | ✅ |
| nagent | nagent(早期版本,仅 Gitee) | 单平台 |
| MD 阅读器 | md-reader-web | ✅ |
| MD 阅读器 | harmonyos-md-reader | ✅ |
| MD 阅读器 | harmonyos-md-reader-lite | ✅ |
| h5game 桌游辅助 | h5game-assistant | ✅ |
| Fun.js | web-spa-demo(参考实现,框架本体未开源) | ✅ |
| 官网 CMS 建站平台 | zry-cms | ✅ |
另有 3 个仓库不对应站上产品,本次未上站:bzm-webframework(自研 Node 全栈框架,仅在 CMS 正文里被提及)、encode-tools、node-web-server。
三、上线内容
数据字段
产品表新增 repos 字段,每行一条,格式:
名称 | 说明 | GitCode 地址 | Gitee 地址
说明与任一平台地址可留空 —— nagent 这类只在单平台开源的仓库照样能挂。后台产品编辑表单里可视化维护,改完刷新产品页即时生效。
前台呈现
- 侧栏「开源仓库」区块:每个仓库显示名称、一句说明,下面两条链接;链接直接显示去掉协议头的完整地址(如
gitcode.com/xxx/yyy),访客能一眼看到地址、也能选中复制,而不是只看到一个平台名。 - 主体区入口:标题下方状态行加「开源仓库 N 个 →」,点击锚到侧栏区块。
- 没有仓库的产品整块不渲染,不会出现空壳区块。
安全处理
链接由前台直接输出到 href,因此在后端先做协议白名单 —— 只放行 http(s),javascript: / data: 一律丢弃;链接里也不允许出现引号与尖括号。前端再用 safeUrl() 做第二道防线,外链统一带 rel="noopener noreferrer"。
这一点不是形式主义:后台字段是人填的文本,若不做白名单,一条
javascript:链接就能在访客浏览器里执行脚本。
文案守住事实
入口写的是「开源仓库 N 个」而非「已开源」。Fun.js 挂的是相关仓库(原生 JS SPA 参考实现),框架本体并未开源,措辞不能替产品打包票 —— 它的仓库说明里已明确写出这一点。
顺手去掉的重复
CMS 产品正文里原本手工写了一张「开源仓库」表格。侧栏区块上线后正文再列一遍就是重复,已把表格移除、保留原有的脱敏说明段落,链接统一由侧栏承载。
四、验收
测试
| 项目 | 结果 |
|---|---|
| 来访统计解析 | 31 / 31 |
| 采集与判定流水线 | 53 / 53 |
| 跨文件契约 | 103 / 103(原 79 条 + 本次新增 24 条) |
| 静态资源引用 | SUCCESS |
| 资源版本号对账 | SUCCESS(153 处引用、140 处去重全部一致) |
本次新增的 24 条契约断言专门锁住这条链:后台表单字段 → 后端写库白名单 → JSON 序列化 → 后端读出口 → 前端消费点 → SQL 建列与数据。因为这条链上任何一处漏了都是静默失效(后台存了但前台不显示,或前台显示了但后台存不进去),光靠人眼审代码很容易漏。
断言做过负向验证:把后端白名单摘掉、把前端 safeUrl() 摘掉,断言各自失败 1 条 —— 证明它们真的能抓到回归,而不是摆设。
页面验收(CDP 实机,本地 + 生产各跑一遍)
- 6 个有仓库的产品:区块、仓库数、链接数、
href指向、target、rel、链接文本、等宽字体、折行全部正确 - 反例:无仓库的产品不出现区块与入口
- 窄屏 390px:MD 阅读器(3 个仓库、地址最长)横向溢出 0px
- 主体区入口点击后正确锚到区块
- 后台表单字段:显示正常、回显 3 段数据、格式提示完整
- 全程 console 零报错
地址核验
从生产接口导出全部 17 条仓库地址逐条访问,全部 200。
五、🔴 顺带发现:数据库连接泄漏(长期潜伏,与本次改动无关)
现象
生产接口日志里反复出现成对报错:
[DBM] getConnection attempt 1 failed: 获取数据库连接超时(5000ms)
[DBM] getConnection attempt 2 failed: Cannot read properties of null (reading 'getConnection')
站点表现为最长约 10 秒的接口失败窗口,然后自愈(下一个请求重建连接池)。
这不是本次改动引入的:改前一天的日志里就有完全相同的错误,属于长期潜伏缺陷,只是被本次的并发验收暴露出来。
定量证据
- 连接池上限 10,实测被打到 10 条全部占用、可用 0 条
- 近 3000 行日志统计:207 次借出,63 条连接借出后从未归还(每条都是「借 1 次、还 0 次」)
- 同期 101 次「获取数据库连接超时」
根因(两处,都在框架的数据库管理层)
① 超时分支把连接漏掉了。 取连接用的是「业务取连接」与「5 秒超时」赛跑。超时赢下之后,业务侧那条取连接的动作仍会继续并最终拿到一条连接,但已经没有人接住它、也就永远不会归还 —— 每超时一次泄漏一条。
后果是自增强的:一次变慢 → 漏掉一条 → 池容量变小 → 更容易超时 → 直到 10 条全漏光。
② 重试时连接池已被置空,却直接又去用它。 重试用尽后代码销毁连接池并置空,但重试循环内部没有重新初始化,于是第二次尝试直接在空对象上取连接,抛出 Cannot read properties of null。
建议补丁
两处都是很小的改动:超时后把迟到兑现的那条连接归还回池(并在成功时清掉定时器);重试循环体第一步先确保连接池已初始化。
为什么没有直接动手:这个文件是框架公共文件,站点每一个请求都要经过它,改错的后果是全站不可用;且需先确认是否被其他进程共用。风险与影响面都超出本次「加链接」的范围,交由您批示后再改。
建议
在您批示前,可以先做一件低风险的事:给数据库连接数加一个监控(当前 16 / 上限 100,还有余量),一旦「借出未归还」的差值持续上涨就能提前发现,而不必等到接口报错。
六、部署与回滚
| 项 | 值 |
|---|---|
| 上传文件 | 18 个(16 个前端含版本号 + 2 个后端 + 1 个 SQL 脚本) |
| 完整性 | 服务器端与本地 双向 md5 全部一致 |
| 数据库 | 升级脚本 20 条语句执行成功,复跑验证幂等 |
| 服务 | 接口进程重启,6 个产品接口返回正确 |
| 缓存 | 构建号已变更,老访客缓存会正确失效;版本号与文件内容哈希逐一对应 |
| 回滚点 | 服务器 /data/_bak/ 保留本次改动前的前端包与后端包各一份 |
回滚方式:解包对应备份即可(SQL 只加了字段与数据,不回滚也不影响旧代码运行)。
七、待批示
- 数据库连接泄漏是否现在修 —— 建议修(两处小改动,但需您确认可以动框架公共文件)
- 是否需要把「已开源」做成更醒目的入口(例如产品列表卡片加标记、页脚加一行「开源仓库」)—— 本次按您的选择只挂了详情页