📁 分享模块调试总结 & 最佳实践

从混乱到清晰:一次完整的文件分享功能修复与架构重构指南
📅 2026-03-12 👤 调试工程师 ⚙️ 最终版本 v3.0 (标准架构)

📌 概述

本文档记录了 smart-upload-backend-v2 项目中分享模块从完全不可用到稳定运行的完整调试历程,并基于此提炼出最优标准实现方案。调试过程中解决了认证缺失、数据库字段错误、路由冲突、权限校验缺失、HTTPS强制、文件信息获取404等一系列问题。最终实现了创建分享、公开访问、下载文件等核心功能。以下内容可作为后续重构或新项目实现的权威参考。

🔥 初始状态

创建分享 → 403/500
获取列表 → 500
公开访问 → 403/500
下载 → 404/500

🔧 修复后状态

创建分享成功 (201)
公开访问成功 (200)
下载直接返回文件流 (200)
支持密码、有效期、权限控制

🚀 标准架构目标

无状态临时令牌
预签名URL直传
302重定向下载
极致性能与可扩展性

🔍 调试问题复盘

本次调试遇到的典型问题及解决方案汇总如下,为后续避免类似错误提供参考。

问题现象根本原因修复方案
创建分享403JWT有效但返回403JwtStrategy返回的用户对象缺少role字段,RolesGuard无法通过AuthService.findUserById中select role,并在JwtStrategy中添加role
路由冲突/shares/user被当作:id捕获动态路由在前,静态路由在后调整路由顺序,删除不必要的/user路径,改用查询参数
字段类型错误malformed array literal数据库file_ids列类型为uuid而非uuid[]执行SQL:ALTER TABLE shares ALTER COLUMN file_ids TYPE uuid[]
手动格式化fileIds返回的fileId"{"fileCount为字符串长度服务层手动将数组格式化为字符串移除手动格式化,让TypeORM自动处理数组
IP拼接错误数据库inet类型插入非法字符串控制器将ip、userAgent等拼接后传给数据库拆分参数,只传递纯IP给服务层
公开访问403访问/public/:code返回403全局JwtAuthGuard未跳过公开路由,或分享要求邮箱/HTTPS添加@Public()装饰器,修改数据库字段require_emailrequire_https
下载权限不足下载返回NO_DOWNLOAD_PERMISSIONaccess_levels中未包含download更新数据库:UPDATE shares SET access_levels = array_append(access_levels, 'download')
下载接口500Cannot read properties of undefined (reading 'shareCode')控制器中result.share为undefinedshare.service.tsdownloadFile中返回完整的file对象
文件详情404获取文件信息接口404控制器要求文件所有者,但分享访问者不是所有者临时前端用分享标题替代,根本方案需改用临时令牌绕过所有权检查

🏗️ 最优标准实施指南

基于调试经验,我们提炼出以下“逻辑与数据分离”的标准架构。该架构可应对高并发、易于扩展,且符合业界最佳实践。

核心思想

应用服务器 (NestJS) 对象存储 (MinIO) │ │ │ 1. 创建分享 (POST /shares) │ │ → 生成分享码, 存元数据 │ │ │ │ 2. 访问分享 (GET /s/{code}) │ │ → 校验有效期/密码等 │ │ → 生成临时访问令牌 (JWT, 1小时) │ │ ← 返回令牌 + 文件元数据 │ │ │ │ 3. 下载文件 (GET /s/{code}/download/{fileId})│ │ → 验证令牌, 检查fileId权限 │ │ → 向MinIO请求预签名URL (60秒) │ │ ← 302重定向到该URL │ │ │ │ 4. 浏览器直接访问预签名URL → 从MinIO下载 │

实施步骤详解

以下按阶段列出需修改的代码文件及核心逻辑,不包含具体代码,仅描述职责和交互

阶段一:引入临时令牌

阶段二:下载改为302重定向

阶段三:优化路径与前端体验

阶段四:增强功能(密码/邮箱/IP限制)

阶段五:可观测性(可选)

关键文件修改清单

文件路径主要修改点
src/modules/share/share.service.ts添加 generateShareToken, verifyShareTokenaccessShare 返回令牌;downloadFile 改为权限验证(不查分享)
src/modules/share/share.controller.ts公开路由返回令牌;下载接口改为302重定向;新增密码验证端点
src/modules/minio/minio.service.ts新增 getPresignedDownloadUrl 方法
src/modules/share/dto/share-response.dto.ts添加 accessToken 字段
src/router/index.js / App.jsx添加路由 /s/:code
src/pages/SharePublic.jsx存储令牌,请求时携带;下载函数简化;支持密码表单
src/components/auth/AuthGuard.jsx/s 加入公开路径白名单
.env添加 SHARE_TOKEN_SECRET 配置

环境配置示例

# .env 新增
SHARE_TOKEN_SECRET=your-strong-secret-for-share-tokens
SHARE_TOKEN_EXPIRY=1h
MINIO_ENDPOINT=www.fox360.cn
MINIO_PORT=9002
MINIO_USE_SSL=false
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=smart-upload

📋 调试经验总结

🔮 未来扩展建议