Files
frontend-miniapp/docs/QA/guide-exhibits-miniapp-data-source-diagnosis-2026-07-02.md
lyf 8fed715235
Some checks failed
CI / verify (push) Has been cancelled
chore: sync latest project updates
2026-07-03 14:42:38 +08:00

21 KiB
Raw Blame History

/guide/exhibits 与 miniapp 讲解接口数据源偏差诊断

诊断日期2026-07-02

范围:只检查“讲解”业务线数据来源,不展开 SDK 地图渲染、楼层、POI、空间面、路网等地图业务。

本次结论基于两类证据:

  • 本地源码:E:\MyWork\深圳国际艺术馆\智慧导览\smart-navigation-system
  • 本项目源码:E:\MyWork\深圳国际艺术馆\museum-guide\museum-guide-v4.0\frontend-miniapp
  • 真实 HTTP 接口:http://1.92.206.90:3001

1. 核心结论

http://1.92.206.90:3001/guide/exhibits 管理端页面展示的数据,与 frontend-miniapp 当前讲解业务接口返回的数据不一致,主要不是因为数据库完全不同,而是因为:

  1. 页面和 miniapp 调用的接口不同。
  2. 接口读取的表层级不同。
  3. 同一张表上的过滤条件不同。
  4. 统计字段的计算口径不同。

管理端 /guide/exhibits 使用的是完整讲解业务数据链:

SGS_EXHIBITION_HALL
  -> SGS_EXHIBIT_OUTLINE
  -> SGS_EXHIBIT_ITEM / SGS_GUIDE_STOP
  -> SGS_GUIDE_CONTENT
  -> sgs_guide_audio_channel

miniapp 当前讲解页虽然已经接入部分 App 端讲解接口,但“业务单元 / 讲解点列表”实际走的是 SDK 地图点位接口:

GET /app-api/gis/sdk/halls/{hallId}/guide-stops

该接口只返回 SGS_GUIDE_STOP 中已启用且已标定 mapX/mapY 的地图点位子集,不等价于管理端 /guide/exhibits 所展示的完整讲解业务数据。

因此,当前偏差的根因可以概括为:

管理端读完整讲解业务结构;
miniapp 列表读 SDK 地图标定子集;
App 展厅统计又直接读 hall 表静态字段;
所以展厅统计、业务单元、讲解点数量、音频状态都会出现偏差。

2. 管理端 /guide/exhibits 页面实际数据来源

2.1 前端项目与页面源码

页面所属项目:

E:\MyWork\深圳国际艺术馆\智慧导览\smart-navigation-system\sgs-frontend-map

页面入口:

sgs-frontend-map/src/app/(main)/guide/exhibits/page.tsx

相关前端文件:

用途 文件
页面组件 sgs-frontend-map/src/app/(main)/guide/exhibits/page.tsx
展厅 / 业务单元 / 展品 API client sgs-frontend-map/src/api/map/exhibit.ts
讲解点 API client sgs-frontend-map/src/api/map/space.ts
左侧树组件 sgs-frontend-map/src/components/v2/exhibits/V2ExhibitTreeBrowser.tsx
讲解模式 / 讲解点面板 sgs-frontend-map/src/components/v2/exhibits/GuideStopsPanel.tsx
Axios baseURL sgs-frontend-map/src/lib/api.ts,默认 /admin-api

2.2 页面实际调用接口

页面区域 前端方法 实际接口
展厅树 ExhibitApi.getTree() GET /admin-api/guide/exhibits/tree
业务单元子节点 ExhibitApi.getChildren(nodeId) GET /admin-api/guide/exhibits/tree/{nodeId}/children
树搜索 ExhibitApi.searchTree() GET /admin-api/guide/exhibits/tree/search
展品列表 ExhibitApi.getPage(params) GET /admin-api/guide/exhibits/list
讲解模式 / 讲解点 StopApi.list(outlineId) GET /admin-api/gis/guide-stop/list?outlineId=...
跨业务单元搜索讲解点 StopApi.listByHallId(hallId) GET /admin-api/gis/guide-stop/list-by-hall?hallId=...

2.3 管理端后端链路

接口 Controller Service / Mapper 主要表
/guide/exhibits/tree V2ExhibitTreeController ExhibitHallMapperSgsExhibitOutlineMapperExhibitItemMapper SGS_EXHIBITION_HALLSGS_EXHIBIT_OUTLINESGS_EXHIBIT_ITEMSGS_GUIDE_CONTENT
/guide/exhibits/tree/{nodeId}/children V2ExhibitTreeController SgsExhibitOutlineMapperExhibitItemMapper SGS_EXHIBIT_OUTLINESGS_EXHIBIT_ITEMSGS_GUIDE_CONTENT
/guide/exhibits/list V2ExhibitCrudController ExhibitServiceImplExhibitItemMapperGuideContentMapper SGS_EXHIBIT_ITEMSGS_GUIDE_CONTENT
/gis/guide-stop/list GuideStopController SgsGuideStopServiceImplSgsGuideStopMapper SGS_GUIDE_STOP
/gis/guide-stop/list-by-hall GuideStopController SgsGuideStopServiceImplSgsExhibitOutlineMapperSgsGuideStopMapper SGS_EXHIBIT_OUTLINESGS_GUIDE_STOP

管理端源码里已经明确说明:

展厅 — SGS_EXHIBITION_HALL
单元 — SGS_EXHIBIT_OUTLINE
展品 — SGS_EXHIBIT_ITEM

对应源码:

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/controller/admin/guide/V2ExhibitTreeController.java

2.4 管理端统计口径

管理端 /guide/exhibits/tree 不直接使用 SGS_EXHIBITION_HALL.exhibitCount 作为展示统计。

它会:

  1. 读取启用展厅:SGS_EXHIBITION_HALL.status = 1
  2. 找到展厅下所有 SGS_EXHIBIT_OUTLINE 后代节点。
  3. 按 outline 聚合 SGS_EXHIBIT_ITEM 数量。
  4. 通过 SGS_GUIDE_CONTENT 判断哪些展品有讲解内容。
  5. 汇总到展厅 / 业务单元节点展示。

因此管理端页面上的展厅数量、业务单元数量、讲解内容数量是“动态聚合结果”,不是 hall 表上一个静态字段。

3. miniapp 当前讲解页实际数据来源

3.1 当前配置

本项目:

E:\MyWork\深圳国际艺术馆\museum-guide\museum-guide-v4.0\frontend-miniapp

配置文件:

.env
src/config/dataSource.ts

当前配置:

VITE_DATA_SOURCE_MODE=sdk
VITE_GUIDE_CONTENT_SOURCE_MODE=remote
VITE_API_BASE_URL=/app-api
VITE_SGS_API_BASE_URL=/app-api

含义:

  • 地图/导览运行模式是 sdk
  • 讲解内容数据源是 remote
  • App API 基础路径是 /app-api
  • sdk 模式不应被理解为讲解业务列表的数据源SDK 只应服务地图渲染/点位层。

3.2 miniapp 前端方法与接口

miniapp 展示数据 前端方法链路 实际接口
展厅列表 ExplainUseCase.loadExplainHalls() -> ExplainRepository.listHalls() -> BackendExplainContentProvider.requestHallList() GET /app-api/gis/hall/list
业务单元统计 loadExplainHallSummaries() -> loadTemporaryBusinessUnitsByHall() -> groupGuideStopsByOutline() 基于 SDK guide-stops 分组
讲解点列表 BackendExplainContentProvider.listGuideStopsByHall() -> sgsSdkApiProvider.getGuideStopsByHall() GET /app-api/gis/sdk/halls/{hallId}/guide-stops
讲解点详情 AudioPlayInfoRepository.getStopInfo() GET /app-api/gis/guide/stop/info
音频播放信息 AudioPlayInfoRepository.getPlayInfo() GET /app-api/gis/guide/audio/play-info
正文 AudioPlayInfoRepository.getTextInfo() GET /app-api/gis/guide/audio/text-info

关键问题:

miniapp 的“业务单元 / 讲解点列表”不是从讲解业务树接口读取,
而是从 SDK 地图点位接口读取。

4. App API 后端读取表与管理端差异

4.1 /app-api/gis/hall/list

Controller

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/controller/app/guide/AppExhibitController.java

接口:

GET /app-api/gis/hall/list

读取:

SGS_EXHIBITION_HALL

过滤:

status = 1

返回 exhibitCount 的方式:

vo.setExhibitCount(h.getExhibitCount())

也就是说App 端 hall/list 的 exhibitCount 直接来自 SGS_EXHIBITION_HALL.exhibitCount 字段。

这与管理端 /guide/exhibits/tree 动态聚合 SGS_EXHIBIT_OUTLINE + SGS_EXHIBIT_ITEM + SGS_GUIDE_CONTENT 的口径不同。

结论:

即使两边都读取 SGS_EXHIBITION_HALL
展厅统计也可能不同。
管理端显示的是动态聚合数量;
App API 返回的是 hall 表静态 exhibitCount 字段。

4.2 /app-api/gis/zone/list-by-hall

Controller

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/controller/app/guide/AppExhibitController.java

接口:

GET /app-api/gis/zone/list-by-hall?hallId=...

源码注释:

展区数据应该从 SGS_EXHIBIT_OUTLINE 读取,这里返回空列表
TODO: 如果需要展区功能,应该查询 SGS_EXHIBIT_OUTLINE 表

结论:

App 端目前没有真正提供与管理端业务单元一致的接口。
管理端业务单元来自 SGS_EXHIBIT_OUTLINE
App 端 zone/list-by-hall 当前返回空数组。

4.3 /app-api/gis/sdk/halls/{hallId}/guide-stops

Controller

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/controller/app/spatial/SdkMapController.java

Service

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/service/sdk/SdkMapServiceImpl.java

接口:

GET /app-api/gis/sdk/halls/{hallId}/guide-stops

读取主表:

SGS_GUIDE_STOP

过滤条件:

status in GuideStopAvailability.AVAILABLE_STATUSES
mapX is not null
mapY is not null

其中 GuideStopAvailability.AVAILABLE_STATUSES = {0, 1},用于兼容历史数据中 0=启用1=启用 两套状态。

源码注释明确说明:

SDK 只输出可落到地图上的讲解点;未标定的属于讲解内容,不进入点位层。

结论:

该接口是 SDK 地图点位接口,不是完整讲解业务列表接口。
它只返回已启用且已地图标定 mapX/mapY 的讲解点。
未标定但有讲解内容、有音频、有正文的讲解点,会被该接口过滤掉。

4.4 /app-api/gis/guide/stop/info

Controller

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/controller/app/guide/AppGuideStopController.java

Service

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/service/guide/AppGuideStopServiceImpl.java

接口:

GET /app-api/gis/guide/stop/info?targetType=STOP&targetId=...&lang=zh-CN

读取:

SGS_GUIDE_STOP
SGS_GUIDE_CONTENT

音频摘要复用:

GuideAudioPlayService

注意:单点详情不需要叠加 mapX/mapY 过滤。GuideStopAvailability.java 源码注释明确说明:

SDK 列表查询需要叠加 mapX/mapY 过滤,但单点 ID 查询stop-info / play-info / text-info不需要叠加。

结论:

同一个 stopId 可能不出现在 SDK 地图列表中,
但仍然可以通过 stop-info / play-info / text-info 查到讲解详情、音频、正文。

4.5 /app-api/gis/guide/audio/play-infotext-info

Controller

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/controller/app/guide/AppGuideAudioController.java

Service

yudao-module-gis/src/main/java/cn/iocoder/yudao/module/gis/service/guide/GuideAudioPlayServiceImpl.java

读取:

SGS_GUIDE_STOP
SGS_GUIDE_CONTENT
sgs_guide_audio_channel

主要逻辑:

  • play-info 判断是否有可播放音频。
  • text-info 判断是否有同语言正文。
  • 播放状态以 SGS_GUIDE_CONTENTsgs_guide_audio_channel 的发布状态为准。
  • 不以 SDK guide-stops 返回的 hasAudio 为准。

结论:

SDK guide-stops 的 hasAudio 不等价于讲解播放服务的 hasAudio / playable。
SDK 列表的 hasAudio 主要看点位自身字段;
play-info 的 playable 看讲解内容和正式音频通道。

5. 真实接口测试结果

本轮复测基础地址:

http://1.92.206.90:3001

5.1 展厅列表

请求:

GET http://1.92.206.90:3001/app-api/gis/hall/list

结果:

HTTP 200
code = 0
msg = ""
data.length = 8

前 5 条:

id name exhibitCount
715792102100832258 宇宙厅 0
715792102100832257 地球厅 0
715792102100832259 演化厅 0
715792102100832260 恐龙厅 0
715792102100832256 人类厅 0

说明:

这里的 exhibitCount 来自 SGS_EXHIBITION_HALL.exhibitCount
不能直接拿来对比管理端 /guide/exhibits 页面动态聚合出的展品/讲解数量。

5.2 宇宙厅 SDK guide-stops

取 hallId

715792102100832258

请求:

GET http://1.92.206.90:3001/app-api/gis/sdk/halls/715792102100832258/guide-stops

结果:

HTTP 200
code = 0
msg = ""
data.length = 2

返回样例:

id name targetType targetId outlineId outlineName hasAudio
1823450596808612 古典星盘 讲解 GUIDE_STOP 1823450596808612 7467940240901013505 第一单元:仰望苍穹——人类对宇宙认识的历程 false
1823450596814245 火星提森特陨石 讲解 GUIDE_STOP 1823450596814245 7467940240901013507 第三单元:采石知天——行星科学与深空探测 false

说明:

这里的 2 条不是宇宙厅完整讲解点数量,
而是宇宙厅下已启用且已标定 mapX/mapY 的 SDK 地图点位数量。

5.3 stop-info

取 guideStop.id

1823450596808612

请求:

GET http://1.92.206.90:3001/app-api/gis/guide/stop/info?targetType=STOP&targetId=1823450596808612&lang=zh-CN

结果:

HTTP 200
code = 0
msg = ""

关键字段:

字段
available true
targetType STOP
targetId 1823450596808612
resolvedStopId 1823450596808612
title 古典星盘 讲解
playTargetType STOP
playTargetId 1823450596808612
hasAudio true
hasText true
audioStatus READY
reason null

5.4 play-info

请求:

GET http://1.92.206.90:3001/app-api/gis/guide/audio/play-info?targetType=STOP&targetId=1823450596808612&lang=zh-CN

结果:

HTTP 200
code = 0
msg = ""

关键字段:

字段
playable true
playUrl 存在
duration 57
audioId 1253
title [古典星盘] 标准解说
reason null

5.5 text-info

请求:

GET http://1.92.206.90:3001/app-api/gis/guide/audio/text-info?targetType=STOP&targetId=1823450596808612&lang=zh-CN

结果:

HTTP 200
code = 0
msg = ""

关键字段:

字段
available true
textLength 269
title [古典星盘] 标准解说
reason null

正文摘要:

这九个16至19世纪的金属星盘是前卫星时代的“科学计算器”集导航、计时、天文功能于一体……

6. 为什么 /guide/exhibits 与 miniapp 接口返回数据不一致

6.1 展厅列表看起来同源,但统计字段不同源

两边都可能读取 SGS_EXHIBITION_HALL,但:

展厅基础表 数量/统计口径
管理端 /guide/exhibits SGS_EXHIBITION_HALL 通过 SGS_EXHIBIT_OUTLINE 后代聚合 SGS_EXHIBIT_ITEMSGS_GUIDE_CONTENT
miniapp /app-api/gis/hall/list SGS_EXHIBITION_HALL 直接返回 SGS_EXHIBITION_HALL.exhibitCount

所以线上 App API 返回的 exhibitCount=0,不能证明管理端页面也应该显示 0。

偏差原因:

管理端动态算;
App API 直接读静态字段。

6.2 业务单元不是同一个接口来源

接口
管理端 /admin-api/guide/exhibits/tree/{hallId}/children SGS_EXHIBIT_OUTLINE
miniapp 当前 /app-api/gis/zone/list-by-hall 源码 TODO当前返回空数组
miniapp 实际展示分组 基于 /app-api/gis/sdk/halls/{hallId}/guide-stopsoutlineId/outlineName 分组 SDK 点位子集

偏差原因:

管理端业务单元来自完整 SGS_EXHIBIT_OUTLINE
miniapp 没有使用等价 App 端 outline 接口,
而是从 SDK guide-stops 返回的少量点位里反推业务单元。

这会导致:

  • 没有已标定讲解点的业务单元不显示。
  • 有业务单元但未落图的讲解点不显示。
  • 业务单元数量比管理端少。

6.3 讲解点列表不是同一个业务口径

接口 过滤
管理端讲解模式面板 /admin-api/gis/guide-stop/list?outlineId=... SGS_GUIDE_STOP outlineId
miniapp 当前列表 /app-api/gis/sdk/halls/{hallId}/guide-stops SGS_GUIDE_STOP status in {0,1}mapX/mapY 非空

偏差原因:

管理端展示讲解业务点;
miniapp 当前展示 SDK 地图可落点。

这两个集合的关系是:

SDK guide-stops ⊂ 管理端讲解业务 guide-stops

即 SDK guide-stops 通常是管理端讲解点中的一个子集。

6.4 音频状态字段不是同一个口径

真实接口已证明同一个 stopId=1823450596808612

接口 字段
/app-api/gis/sdk/halls/{hallId}/guide-stops hasAudio false
/app-api/gis/guide/stop/info hasAudio true
/app-api/gis/guide/audio/play-info playable true
/app-api/gis/guide/audio/text-info available true

偏差原因:

SDK guide-stops 的 hasAudio 主要看 SGS_GUIDE_STOP.audioUrl
讲解播放接口的 playable 看 SGS_GUIDE_CONTENT 与 sgs_guide_audio_channel。

所以 SDK 列表中的 hasAudio=false 不应作为讲解业务播放状态。

7. 与接口契约的关系

docs/miniapp_integration.md 中的 App 端讲解接口契约约定:

  • stop-info 用于进入讲解页时获取标题、图片、绑定展品、当前语言音频/正文状态。
  • play-info 用于点击播放时获取唯一可播放 playUrl
  • text-info 用于展开正文时按需获取讲解词全文。
  • available=false / playable=false 是业务不可用,不一定代表 HTTP 失败。
  • 不可用响应仍可返回 code=0,客户端应看 data.availabledata.playablereason

这套契约解决的是“单个 ITEM / STOP 的展示、播放、正文”问题。

它没有解决:

按展厅获取完整业务单元树;
按业务单元获取完整讲解点列表;
按管理端相同口径统计展品数/讲解数。

因此当前 miniapp 需要补充“讲解业务列表 / 树”类 App API而不是继续用 SDK 地图点位接口替代。

8. 推荐修复方向

8.1 后端补 App 端讲解业务接口

建议新增或实现以下接口之一:

GET /app-api/gis/guide/halls/{hallId}/outlines
GET /app-api/gis/guide/outlines/{outlineId}/stops

或组合接口:

GET /app-api/gis/guide/halls/{hallId}/explain-tree

接口读取口径应对齐管理端:

SGS_EXHIBITION_HALL
SGS_EXHIBIT_OUTLINE
SGS_GUIDE_STOP
SGS_EXHIBIT_ITEM
SGS_GUIDE_CONTENT
sgs_guide_audio_channel

要求:

  • 不按 mapX/mapY 过滤讲解点。
  • 业务单元从 SGS_EXHIBIT_OUTLINE 获取。
  • 讲解点从 SGS_GUIDE_STOP 获取。
  • 展品绑定关系从 SGS_EXHIBIT_ITEM.stopId 获取。
  • 音频状态复用 GuideAudioPlayService 的摘要口径。
  • 展厅/业务单元统计口径对齐管理端 /guide/exhibits/tree

8.2 前端替换列表数据源

miniapp 前端应调整:

BackendExplainContentProvider.listGuideStopsByHall()
BackendExplainContentProvider.listTemporaryBusinessUnitsByHall()

不要再从:

/app-api/gis/sdk/halls/{hallId}/guide-stops

生成完整讲解业务单元和讲解点列表。

SDK guide-stops 可以保留为“查看位置 / 地图落点预览”的辅助数据,但不能作为讲解业务完整列表数据源。

8.3 展厅统计修复

/app-api/gis/hall/listexhibitCount 当前直接来自 SGS_EXHIBITION_HALL.exhibitCount

如果 miniapp 需要展示与管理端一致的统计,应选择其一:

  1. 后端在 hall/list 中动态聚合管理端同口径统计。
  2. 新增 guide/halls/{hallId}/explain-tree 时返回统计。
  3. 定期维护 SGS_EXHIBITION_HALL.exhibitCount,并明确它与管理端动态统计一致。

推荐优先采用第 2 种:在讲解业务树接口中返回统计,避免让通用 hall/list 承担复杂业务聚合。

9. 最终判断

/guide/exhibits 页面展示数据和 frontend-miniapp 当前接口返回数据不一致,根因不是简单的“接口坏了”,而是:

管理端 /guide/exhibits 读取完整讲解业务表,并动态聚合统计;
miniapp 当前展厅列表读取 hall 表静态 exhibitCount
miniapp 当前业务单元和讲解点列表读取 SDK 地图标定点位子集;
miniapp 单点详情/播放/正文又读取讲解业务内容表和音频通道表。

因此当前 miniapp 同时混用了:

  • 讲解业务数据源:SGS_EXHIBITION_HALLSGS_GUIDE_STOPSGS_GUIDE_CONTENTsgs_guide_audio_channel
  • SDK 地图数据源/点位口径:SGS_GUIDE_STOP 中已标定 mapX/mapY 的子集
  • 静态统计字段:SGS_EXHIBITION_HALL.exhibitCount

正确方向是把“讲解业务列表 / 业务单元 / 讲解点数量 / 讲解模式”统一切回讲解业务接口SDK 接口只作为地图落点和位置预览的辅助数据。