Iconify在纯内网环境下的处理方式,不使用docker自建

1. 背景与问题

项目管理后台基于 soybean-admin-element-plus,图标主要使用 Iconify 编码,原有 @iconify/vue 使用按需加载模式。页面只保存图标名称,浏览器首次渲染时再向 Iconify API 请求 SVG 数据。该模式在互联网环境下使用方便,但在纯内网部署时会产生以下问题:

  1. 浏览器无法访问公网 Iconify 服务,菜单、按钮图标显示为空。
  2. 图标加载失败会增加页面请求超时、控制台报错和首屏等待时间。
  3. 依赖外部图标服务不符合生产静态资源随应用交付、网络边界可控的内网部署要求。
  4. docker自建方式运维复杂,系统离线部署太多不可控因素。

因此,本项目需要将通用图标改为“构建时提取、随前端产物发布、运行时只读本地集合”的离线模式。

2. 目标与非目标

2.1 建设目标

  • 生产环境浏览器不请求 Iconify 公网 API、CDN 或其他外部图标服务。
  • 保持已有 mdi:*ph:*carbon:*ant-design:* 图标编码,避免数据库大规模迁移。
  • 动态菜单、门户应用、权限路由等场景可以继续通过编码渲染图标。
  • 图标随前端 dist 一起交付,前端服务器只需托管静态文件即可。
  • 图标清单可审查、可复现、可在构建阶段发现错误图标编码。
  • 对未知图标提供本地占位显示,不因异常编码触发网络回退。

2.2 非目标

  • 不在运行时建设或维护通用 Iconify 内网服务。

3. 方案选型

方案运行时是否联网交付复杂度图标扩展方式评估结论
构建时内置受控图标集更新清单后重新构建采用
自建内网 Iconify API仅访问内网服务中高服务端更新图标包仅适合有独立图标服务运维能力的场景
全部改为本地 SVG 文件新增 SVG 和名称映射可作为业务专用图标补充
自建图标字体维护字体和编码表不采用,调试和可访问性较弱
浏览器缓存 / Service Worker不可靠依赖历史访问结果不采用,不能作为内网保障

3.1 采用构建时内置的原因

项目已有以下基础条件:

  • package.json 已锁定 @iconify/json,可以在构建阶段读取标准 Iconify JSON 图标集。
  • @iconify/vue 提供 offline 入口,支持注册本地集合后渲染,不启用 API 加载器。
  • Vite 已统一负责生产前端构建,可以在构建阶段生成虚拟模块。
  • src/assets/svg-icon 已有本地 SVG 精灵机制,可继续承载项目自有图标和占位图标。

该方案不增加生产服务器、不增加数据库表、不增加运行时网络依赖,并且保留现有 Iconify 名称,改造边界最小。

4. 总体架构

┌──────────────────────┐
│ @iconify/json         │  已锁定的开源图标包
└──────────┬───────────┘
           │ 构建阶段读取
           ▼
┌──────────────────────┐
│ offline-iconify Vite  │  按受控清单提取图标
│ plugin                │  生成 virtual:offline-iconify
└──────────┬───────────┘
           │ 打包进 JS
           ▼
┌──────────────────────┐
│ @iconify/vue/offline  │  注册本地集合
└──────────┬───────────┘
           │ 运行时按名称查找
           ▼
┌──────────────────────┐
│ SvgIcon               │  动态菜单、门户、按钮图标
└──────────────────────┘

静态组件图标(例如 <icon-mdi-drag />)由 unplugin-icons 在构建期直接编译为本地 SVG,同样不需要运行时访问网络。

运行时请求边界如下:

资源类型来源是否允许外部请求
通用 Iconify 图标前端 JS 内置集合
src/assets/svg-icon 自有 SVG前端静态资源 / SVG symbol
门户应用上传图标同域 /media仅允许同域受控资源
门户 Logo、背景图同域 /media仅允许同域受控资源

5. 详细设计

5.1 受控图标清单

清单位于:

web-admin/src/constants/iconify.ts

清单包含源码静态引用、菜单种子、门户默认图标和已知历史数据中使用的 Iconify 名称,覆盖主要的 mdiphosphorcarbonant-design 图标集。

清单设计原则:

  1. 只纳入项目实际使用或经过审核允许配置的图标。
  2. 使用完整的 prefix:name 编码,避免不同图标集同名冲突。
  3. 清单变更必须随前端版本提交并重新构建。
  4. 清单不从数据库或外部接口动态扩展,避免运行时资源不可预测。
  5. 发现历史别名或下线图标时,优先添加显式本地回退映射,而不是恢复公网请求。

当前已对历史 ph:chart-line-bars 做本地回退,将其映射到可用的 ph:chart-line。该回退只影响图标显示,不修改数据库中的历史菜单值。

5.2 Vite 构建插件

实现文件:

web-admin/build/plugins/offline-iconify.ts

构建插件执行以下步骤:

  1. 读取 offlineIconifyNames
  2. 按前缀分组,例如 mdiphcarbon
  3. @iconify/json/json/{prefix}.json 读取完整图标集。
  4. 仅复制清单中的 icons 数据,保留必要的宽高信息。
  5. 生成 virtual:offline-iconify 虚拟模块。
  6. 由 Vite 将虚拟模块内容打入前端产物。

若清单中的图标在已锁定图标包中不存在,构建插件直接抛出错误,使问题在构建阶段暴露,而不是部署后才发现页面空白。

5.3 运行时注册

实现文件:

web-admin/src/plugins/iconify.ts

应用启动时从虚拟模块读取图标集合,并调用 addCollection 注册到 @iconify/vue/offline 的本地存储。该入口不包含 Iconify API 加载器,因此图标未命中时不会向公网发起请求。

5.4 统一图标渲染组件

实现文件:

web-admin/src/components/custom/svg-icon.vue

渲染优先级:

  1. 传入 localIcon 时,使用已有 SVG symbol 精灵。
  2. 传入受控 Iconify 编码时,从本地 Iconify 集合渲染。
  3. 传入历史回退编码时,先转换为本地可用编码再渲染。
  4. 传入未知编码时,使用 no-icon 本地占位图标。

该组件不拼接外部 URL,也不根据图标名称生成 <img src="https://...">

5.5 静态组件图标

项目中通过 unplugin-icons 使用的组件图标,例如:

<icon-mdi-drag />
<icon-ant-design-reload-outlined />

由构建工具在编译阶段转换为本地 SVG。此类图标不经过运行时 SvgIcon 网络加载流程,也不依赖公网 API。

5.6 动态配置图标

以下数据仍然可以保存 Iconify 编码:

  • sys_menu.icon
  • 门户应用的 icon_key

前端展示时统一经过 SvgIcon 的本地集合解析。管理端选择器只展示受控图标清单;历史数据库中暂不在清单内的编码显示占位图标,避免页面因脏数据触发外部请求。

门户应用的 FILE 图标类型仍使用现有同域上传文件能力,适用于彩色业务 Logo 或业务专用图片,不与通用 Iconify 图标混用。

6. 资源体积与性能

6.1 只打包受控图标

不直接把完整 MDI、Phosphor 或 Material Symbols 图标库打入浏览器,而是在构建阶段裁剪为项目清单。因此:

  • 首屏不需要逐个请求图标。
  • 浏览器不产生图标 API 请求。
  • 图标数据随主 JS 或代码分片由静态服务器一次提供。
  • 新增图标的体积影响可以在构建产物中直接审查。

6.2 缓存策略

生产环境建议继续使用现有 Vite 文件名哈希和 Nginx 静态缓存策略:

  • 带哈希的 JS、CSS、SVG 文件可设置较长缓存时间。
  • index.html 使用较短缓存或按现有发布策略处理。
  • 图标变更随前端版本发布,不能只替换服务器上的某个图标文件而不更新 JS 产物。

7. 方案优缺点

7.1 优点

  • 完全适配纯内网和隔离网络。
  • 不新增生产服务和运维端口。
  • 保留现有图标编码和数据库结构,迁移成本低。
  • 构建期校验,问题比运行时网络失败更早暴露。
  • 图标来源、版本和许可证可追踪。
  • 未知图标有确定的本地降级行为。

7.2 限制

  • 新增图标需要修改清单并重新构建前端。
  • 动态配置不能任意使用完整 Iconify 社区图标库。
  • 受控清单过大时会增加前端包体积,需要定期清理未使用图标。
  • 图标包升级可能带来名称、路径或 SVG 内容变化,需要重新执行构建验证。

8. 后续优化建议

  1. 在 CI 中增加“源码/菜单种子图标编码覆盖率”和“产物公网地址扫描”两个门禁。
  2. 定期从数据库导出有效菜单与门户图标编码,与前端清单做差异检查。
  3. 对菜单管理和门户管理增加服务端受控编码校验,减少新的未知图标进入数据库。
  4. 将图标清单和开源许可证信息纳入版本发布物料。
  5. 对业务专用彩色图标优先使用同域上传文件或本地 SVG,不扩张通用 Iconify 清单。

9. 结论

本方案将 Iconify 从“运行时远程资源”调整为“构建时受控静态资源”,满足纯内网部署对网络隔离、可重复构建和静态交付的要求。现有菜单和门户图标编码可以继续使用,前端通过本地集合、历史回退和占位图标保证渲染稳定性;部署侧无需增加图标服务,后续维护简单,比跑docker更方便,当然也有其短处,如果都是大型项目,自建docker服务应该更好,如果只是单机构的小型项目,本方案较好。