Files
tools-show/design/specs/2026-04-14-tool-display-version-none-mode-design.md
2026-04-15 13:47:39 +08:00

153 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工具展示版本与无访问模式设计规格
## 概述
当前工具站只区分 `web``download` 两种访问方式。下载工具的版本来源于 `ToolArtifact.version`,因此访客端首页和详情页中的“版本”信息只对下载工具有效,网页工具长期显示“暂无版本”。与此同时,当前系统没有“仅展示详情、不提供打开或下载入口”的访问模式,无法承载预告型、资料型或人工分发型工具。
本次设计目标是在现有 `Tool` 模型上补齐“展示版本”和“无访问模式”能力,保持下载包管理逻辑不变,同时让前台和后台统一拿到一个稳定的展示版本字段。具体策略为:数据库新增 `Tool.versionOverride` 作为人工维护的展示版本覆盖值;服务端统一计算 `displayVersion`;访问方式新增 `none`,用于“只展示详情,不执行 launch”的工具。
## 目标定位
- **主要用户**:后台管理员、公开站访客
- **使用场景**:管理员在新增或编辑工具时维护展示版本,并可将工具设置为 `download``web``none`;访客在首页和详情页查看统一的版本信息,其中 `none` 模式工具只提供“查看详情”入口,不提供打开网页或下载操作
## 约束条件
- **技术限制**:前端基于 Vue 3 + Element Plus服务端基于 NestJS + Prisma + SQLite本次只做现有模型上的增量改造不引入新的依赖或新的版本历史表
- **时间限制**:本轮只实现版本展示和 `none` 访问模式,不扩展版本历史、发布日期、版本说明等二级能力
- **资源限制**:继续复用现有下载包模型、公开列表/详情接口和后台工具管理流程;版本字段仅用于展示,不参与发布校验、不新增额外审核流程
## 成功标准
- [ ] Prisma 数据模型支持 `AccessMode.none``Tool.versionOverride`
- [ ] 后台新增/编辑工具表单可以维护“展示版本”,访问方式下拉可选择“无”
- [ ] 后台工具列表接口和公开工具列表/详情接口统一返回 `displayVersion`
- [ ] 下载工具的 `displayVersion` 计算规则为 `versionOverride || latestArtifact.version || null`
- [ ] 网页工具和无访问模式工具的 `displayVersion` 计算规则为 `versionOverride || null`
- [ ] 访客端首页和详情页统一展示 `displayVersion`,不再依赖下载模式专属的 `latestVersion`
- [ ] `none` 模式在首页主按钮显示“查看详情”,详情页不展示打开/下载主按钮
- [ ] 直接调用 `launch` 接口访问 `none` 模式工具时返回明确错误,且不会产生打开/下载计数
- [ ] 现有发布约束保持不变:`web` 仍要求 `openUrl``download` 仍要求安装包或下载地址,`none` 不因为缺少版本而被阻止发布
- [ ] 后台概览和模式标签不再把 `none` 模式误显示为“下载模式”
## 架构设计
### 系统结构
本次改造分为四层:
1. **数据层**:在 `Tool` 模型中新增 `versionOverride` 字段;在 `AccessMode` 枚举中新增 `none`
2. **映射层**:在后台服务和公开服务中统一计算 `displayVersion`,将“原始覆盖值”和“最终展示值”分离
3. **行为层**:在 `AccessService` 中显式拒绝 `none` 模式的 launch 行为,保证服务端语义闭合
4. **展示层**:后台编辑页维护 `versionOverride`,后台列表和访客端页面统一展示 `displayVersion`
版本数据的核心原则如下:
1. `versionOverride` 是管理员手工维护的原始字段,允许为空
2. `displayVersion` 是接口返回给前端的最终展示值,不直接持久化
3. 对下载工具,若 `versionOverride` 为空,则自动回退到当前 `latestArtifact.version`
4. 对网页工具和无访问模式工具,`displayVersion` 只来自 `versionOverride`
5. `latestArtifact.version` 继续用于安装包管理;`displayVersion` 用于页面展示
访问模式的核心原则如下:
1. 新增枚举值 `none`,内部值固定为 `none`,后台展示文案为“无”
2. `none` 模式工具允许存在详情页和展示版本,但不允许执行 launch
3. 访客端列表页对 `none` 模式的主按钮改为“查看详情”
4. 访客端详情页对 `none` 模式不渲染打开/下载按钮
5. 服务端若收到对 `none` 模式的 launch 请求,返回 `409 Conflict`
### 组件划分
| 组件 | 职责 | 依赖 |
|------|------|------|
| `server/prisma/schema.prisma` | 为 `AccessMode` 增加 `none`;为 `Tool` 增加 `versionOverride` 字段 | Prisma |
| `server/prisma/seed.ts` | 补齐种子数据中的访问方式和展示版本样例,避免本地初始化后看不到新行为 | Prisma Seed |
| `server/src/modules/admin-tools/dto/create-tool.dto.ts` | 接收 `versionOverride`,并允许 `accessMode=none` | `class-validator` |
| `server/src/modules/admin-tools/dto/update-tool.dto.ts` | 继承新增字段,支持后台更新展示版本 | `CreateToolDto` |
| `server/src/modules/admin-tools/dto/update-access-mode.dto.ts` | 接收 `none` 枚举值,兼容访问方式切换 | `class-validator` |
| `server/src/modules/admin-tools/admin-tools.service.ts` | 规范化 `versionOverride`;列表和详情返回 `displayVersion``versionOverride`;保持发布校验逻辑不因版本字段变化 | Prisma |
| `server/src/modules/tools/tools.service.ts` | 公开列表和详情统一返回 `displayVersion`;保留下载工具的 `latestVersion` 作为兼容字段 | Prisma |
| `server/src/modules/access/access.service.ts` | 对 `none` 模式返回明确错误,禁止生成 launch 地址或下载票据 | Prisma, ConfigService |
| `server/src/modules/admin-overview/admin-overview.service.ts` | 访问方式统计兼容 `none`,避免概览数据遗漏或模式显示错误 | Prisma |
| `client/src/admin/components/ToolFormDialog.vue` | 新增“展示版本”输入项;访问方式下拉新增“无”;根据模式调整提示文案 | Element Plus |
| `client/src/admin/components/AdminToolsSection.vue` | 列表增加版本列,访问方式标签兼容 `none` 文案 | Element Plus |
| `client/src/admin/components/AccessModeDialog.vue` | 快速修改访问方式时允许选择“无”,并在 `none` 模式下隐藏 URL 输入项 | Element Plus |
| `client/src/admin/AdminApp.vue` | 表单数据结构新增 `versionOverride`;构造创建/更新 payload访问模式、标签颜色和模式文案兼容 `none` | Vue 3 |
| `client/src/App.vue` | 工具卡片统一显示 `displayVersion``none` 模式主按钮改为“查看详情”并直接路由跳转 | `vue-router`, `client/src/api.js` |
| `client/src/pages/ToolDetailPage.vue` | 摘要区展示 `displayVersion``none` 模式不显示主操作按钮 | `vue-router`, `client/src/api.js` |
| `client/src/admin/components/AdminOverviewSection.vue` | 模式统计与 Top 工具模式文案兼容 `none` | Element Plus |
## 数据流
### 版本字段维护流
1. 管理员在新增或编辑工具时填写“展示版本”
2. 前端将该值作为 `versionOverride` 提交给后台;空字符串在提交前或服务端落库前统一归一化为 `null`
3. 服务端保存 `Tool.versionOverride`
4. 当后台列表、后台详情、公开列表或公开详情查询工具时,服务端统一计算 `displayVersion`
5. 前端页面一律渲染 `displayVersion`,不再直接依赖下载工具专属的 `latestVersion`
`displayVersion` 计算规则如下:
1.`tool.accessMode === 'download'` 时:`displayVersion = tool.versionOverride || tool.latestArtifact?.version || null`
2.`tool.accessMode === 'web'``tool.accessMode === 'none'` 时:`displayVersion = tool.versionOverride || null`
为降低回归风险,下载工具响应中的 `latestVersion` 字段继续保留,其语义保持为“当前最新安装包版本”;页面展示逻辑统一切换为 `displayVersion`
### 访问模式切换流
1. 管理员在新增/编辑表单或访问方式对话框中选择 `download``web``none`
2. 当模式为 `web` 时,沿用现有规则,发布时仍要求 `openUrl`
3. 当模式为 `download` 时,沿用现有规则,发布时仍要求活动安装包或外部下载地址
4. 当模式为 `none` 时,不生成 launch 目标,不要求 `openUrl`,前端不展示打开/下载按钮
5. 当公开站列表遇到 `none` 模式工具时,主按钮直接路由到详情页
6. 当公开站详情页遇到 `none` 模式工具时,仅展示内容和版本,不发起 launch 请求
7. 若外部调用者直接请求 `POST /api/v1/tools/:id/launch` 且目标工具为 `none` 模式,服务端返回 `409`
### 前后台显示流
1. 后台工具列表新增版本列,显示 `row.displayVersion || '-'`
2. 公开首页卡片“版本”行显示 `tool.displayVersion || '暂无版本'`
3. 公开详情页摘要区显示 `detail.displayVersion || '暂无版本'`
4. `none` 模式的卡片副文案和详情摘要不再显示“下载/访问”动词,统一显示“仅详情”
5. 概览和模式标签遇到 `none` 时显示“无”,不将其归并到下载模式
## 错误处理
| 错误类型 | 处理方式 |
|----------|----------|
| `versionOverride` 为空字符串 | 前端提交前或服务端保存前统一归一化为 `null`,避免空串污染数据 |
| 下载工具没有覆盖版本且没有最新安装包版本 | `displayVersion` 返回 `null`,前端显示“暂无版本”,但不阻止发布之外的页面展示 |
| `none` 模式被直接调用 launch 接口 | 服务端返回 `409 Conflict`,建议复用 `TOOL_ACCESS_MODE_MISMATCH` 并提供明确消息如“tool is not launchable in none mode” |
| 访问方式统计遇到 `none` | 后台概览必须显式处理该值,避免统计遗漏或错误文案 |
| 旧前端代码仍读取 `latestVersion` | 下载工具继续保留该字段,版本展示迁移到 `displayVersion` 后逐步收敛消费端 |
| 管理员把工具切换为 `none` | 页面行为以模式为准,版本仍可展示,但公开站不再允许打开/下载 |
## 风险评估
| 风险描述 | 类别 | 影响 | 概率 | 缓解策略 |
|----------|------|------|------|----------|
| 新增 `none` 枚举后,现有前端分支把未知模式错误地当成下载模式处理 | 集成风险 | 高 | 中 | 全量检查 `accessMode` 条件分支,补齐 `none` 的按钮文案、标签颜色和统计展示 |
| 下载工具同时存在 `latestArtifact.version``versionOverride`,容易出现展示口径不一致 | 技术风险 | 中 | 中 | 服务端统一生成 `displayVersion`,前端禁止自行拼接版本来源 |
| 旧接口调用方继续消费 `latestVersion`,导致 web/none 工具仍显示“暂无版本” | 范围风险 | 中 | 中 | 在页面和后台列表统一切到 `displayVersion`,同时短期保留 `latestVersion` 兼容下载工具 |
| 增加 `none` 模式后,后台概览和模式统计遗漏该值 | 集成风险 | 中 | 中 | 同步更新概览服务、模式标签和概览页面文案,避免出现统计不闭合 |
## 测试策略
- **单元测试**:为服务端新增版本解析逻辑编写测试,覆盖三种场景:下载工具无覆盖值时回退安装包版本、下载工具有覆盖值时优先显示覆盖值、`web/none` 工具只使用覆盖值
- **集成测试**:覆盖后台创建/更新工具时提交 `versionOverride``accessMode=none`;覆盖公开列表和详情接口返回 `displayVersion`;覆盖 `none` 模式 launch 接口返回 `409`
- **前端组件测试**:覆盖首页卡片在 `none` 模式下显示“查看详情”;覆盖详情页在 `none` 模式下不渲染主操作按钮;覆盖版本展示优先使用 `displayVersion`
- **验收标准**:管理员可维护展示版本;后台工具列表可看到版本;访客端列表与详情都能看到统一版本;下载工具默认继承安装包版本;`none` 模式只可查看详情、不可 launch后台概览与模式文案不出现错误映射
## 决策记录
| 决策 | 理由 | 影响 |
|------|------|------|
| 使用 `Tool.versionOverride + displayVersion`,而不是为所有工具新建统一版本表 | 当前需求只需要单值展示和少量覆盖能力,不需要版本历史,增量成本最低 | 需要在服务端增加统一映射逻辑 |
| `displayVersion` 由服务端计算,不在数据库落地 | 避免前端自行拼接版本来源,减少 download/web/none 三种模式的歧义 | 后台和公开接口都要增加该字段 |
| 下载工具保留 `latestVersion`,但页面展示切到 `displayVersion` | 降低旧代码回归风险,同时给新页面一个统一字段 | 服务端响应短期内会同时存在两个版本相关字段 |
| 访问方式新增 `none`,而不是用布尔开关控制是否可 launch | 与现有 `AccessMode` 模型保持一致,语义清晰,便于后台筛选和统计 | Prisma 枚举、前后端条件分支和概览统计都需要更新 |
| `none` 模式首页主按钮跳详情页,详情页不展示主操作按钮 | 用户已明确选择详情引导型行为,比“禁用按钮”更可用 | 公开站首页和详情页都需增加 `none` 分支 |
| 版本字段不参与发布校验 | 用户明确要求版本只作展示,不希望改变当前发布约束 | `assertPublishInput` 和状态流保持现状,只需兼容 `none` 模式 |