Sharely — 技术文档

版本: 1.0.0 · 许可证: MIT · Node.js: ≥ 18

目录

1. 项目概述

2. 架构

3. 技术栈

4. 目录结构

5. 配置与环境变量

6. 数据模型

7. REST API

8. WebSocket API

9. 文件服务路由

10. 身份验证与安全

11. 上传系统

12. 缩略图生成

13. 邮件与SMTP

14. 国际化

15. 前端(React SPA)

16. 管理后台

17. GDPR / 隐私保护

18. 后台任务

19. 部署

20. 开发环境

21. 迁移与脚本

22. 端到端测试


1. 项目概述

Sharely 是一个自托管文件共享平台,提供简洁的 Web 界面、ShareX 集成和 API 访问。用户可上传截图、文件和媒体,并通过短链接即时分享。

核心功能

功能描述
Web 上传拖放上传,单次最多支持 500 个文件
分块上传通过并行多部分上传支持最大 2GB 的文件
ShareX 集成一键下载 .sxcu 配置文件
API 上传Bearer Token 认证,兼容 curl/wget
文件查看器图片缩放、视频/音频流媒体(HTTP Range)、PDF 内联显示、代码语法高亮
嵌入模式*embed*(OG/Twitter Card HTML)或 *raw*(直接重定向)
缩略图自动为视频(ffmpeg)和 PDF(ghostscript)生成 JPEG 预览图
集合支持可选密码和过期日期的文件分组
分享链接带密码、过期时间和下载次数限制的文件专属链接
实时 UI基于 WebSocket 的实时更新(上传、删除、访问计数、管理统计)
多语言支持8 种语言:EN、DE、FR、ES、IT、PT、JA、ZH
管理后台统计信息、用户管理、文件管理、审计日志(CSV 导出)
GDPR 合规符合欧盟 GDPR 的隐私功能(第 17、20、32 条等)
XBackBone 导入支持从现有 XBackBone 安装迁移数据
Docker 就绪docker compose up -d 即可启动完整环境

2. 架构

┌─────────────────────────────────────────────────────────────┐
│                        Browser / Client                      │
│            React 18 SPA  ·  Vite  ·  Tailwind CSS           │
│   ┌──────────────────────────────────────────────────────┐   │
│   │  HTTP REST (/api/*)            WebSocket (/ws)       │   │
│   └──────────────────────────────────────────────────────┘   │
└───────────────────────────┬────────────────────┬─────────────┘
                            │ HTTP               │ WS
┌───────────────────────────▼────────────────────▼─────────────┐
│                    Express.js (app.js)                        │
│                                                               │
│  ┌──────────────┐  ┌───────────┐  ┌──────────┐  ┌────────┐  │
│  │  /api/auth   │  │  /api/*   │  │   /f/*   │  │  /s/*  │  │
│  │  /api/install│  │  routes   │  │  files   │  │ shares │  │
│  └──────────────┘  └───────────┘  └──────────┘  └────────┘  │
│                                                               │
│  Middleware: session · rate-limit · CSRF · CSP · auth        │
│                                                               │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────┐  │
│  │  multer  │  │  ws.js   │  │  mailer  │  │ retention  │  │
│  │  upload  │  │ WebSocket│  │ nodemailer│  │  cleanup   │  │
│  └──────────┘  └──────────┘  └──────────┘  └────────────┘  │
└───────────────────────────┬───────────────────────────────────┘
                            │
┌───────────────────────────▼───────────────────────────────────┐
│                      MongoDB (Mongoose)                        │
│  User · File · Collection · ShareLink · SiteSettings          │
│  AuditLog                                                      │
└───────────────────────────────────────────────────────────────┘
                            │
              ┌─────────────▼───────────────┐
              │     Dateisystem (uploads/)   │
              │  {folderName}/  ·  .chunks/  │
              │  .thumbnails/   ·  .avatars/ │
              └─────────────────────────────┘

数据流:标准上传

Browser → POST /api/web-upload (multipart/form-data)
       → multer: Datei in uploads/{folderName}/
       → File.createUnique() → MongoDB
       → generateThumbnail() (async, non-blocking)
       → logAudit()
       → broadcast('file:uploaded') via WebSocket
       ← JSON: { files: [...] }

数据流:ShareX 上传

ShareX → POST /upload (token im Formular-Body)
       → multer: Datei temporär in uploads/
       → requireApiKey: Token-Lookup → User
       → fs.renameSync → uploads/{folderName}/
       → File.create() → MongoDB
       ← JSON: { url, delete_url }

3. 技术栈

层级技术版本
运行时Node.js≥ 18
后端框架Express.js4.x
数据库MongoDB + MongooseMongo 7, Mongoose 8.x
会话管理express-session + connect-mongo
实时通信WebSocket (ws)8.x
文件上传Multer1.x
邮件Nodemailer8.x
密码哈希bcryptjs2.x(12 轮)
API 密钥哈希SHA-256(Node Crypto)
限流express-rate-limit8.x
XBackBone 导入sql.js1.x
前端框架React 1818.3.x
前端路由React Router v66.x
构建工具Vite6.x
样式Tailwind CSS + Radix UI3.x
UI 组件shadcn/ui(Radix primitives)
图标FontAwesome6.x
i18ni18next + react-i18next
语法高亮highlight.js11.x
容器Docker + Docker Compose
测试Playwright(E2E)1.60.x

4. 目录结构

sharely/
├── app.js                          # Express 入口点,启动序列
├── package.json
├── .env.example                    # 所有环境变量的模板
├── Dockerfile
├── docker-compose.yml
│
├── src/
│   ├── config/
│   │   └── db.js                   # MongoDB 连接(Mongoose)
│   ├── middleware/
│   │   ├── auth.js                 # requireLogin / requireAdmin / requireApiKey
│   │   └── upload.js               # Multer 配置,黑名单
│   ├── models/
│   │   ├── AuditLog.js             # 审计事件(TTL 90 天)
│   │   ├── Collection.js           # 文件集合
│   │   ├── File.js                 # 文件元数据
│   │   ├── ShareLink.js            # 文件专属分享链接
│   │   ├── SiteSettings.js         # 单例:运营者设置
│   │   └── User.js                 # 用户账户 + API 密钥
│   ├── routes/
│   │   ├── api.js                  # 主 API(上传、相册、管理等)
│   │   ├── auth.js                 # 登录 / 注册 / 密码重置
│   │   ├── files.js                # 文件服务、OG 嵌入、Range 请求
│   │   ├── import.js               # XBackBone 迁移
│   │   ├── install.js              # 初始安装端点
│   │   └── shares.js               # 分享链接文件服务
│   ├── jobs/
│   │   └── retentionCleanup.js     # 每日删除过期文件
│   ├── migrations/
│   │   ├── migrateApiKeyHashes.js  # 一次性:明文 → SHA-256 哈希
│   │   └── migrateUserFolders.js   # 一次性:将文件移入用户文件夹
│   ├── utils/
│   │   ├── audit.js                # logAudit() 辅助函数
│   │   ├── generateThumbnail.js    # ffmpeg / ghostscript 集成
│   │   ├── mailer.js               # Nodemailer 封装 + i18n 邮件模板
│   │   └── sanitizeFilename.js     # 文件名净化
│   └── ws.js                       # WebSocket 服务器 + 动作调度器
│
├── client/                         # React 前端
│   ├── index.html
│   ├── package.json
│   ├── vite.config.js
│   ├── tailwind.config.js
│   └── src/
│       ├── main.jsx                # React 入口点
│       ├── App.jsx                 # 路由配置
│       ├── index.css               # 全局样式
│       ├── context/
│       │   └── AuthContext.jsx     # 全局认证状态
│       ├── hooks/
│       │   ├── use-toast.js        # Toast 通知 Hook
│       │   └── useWebSocket.js     # WS 连接 + 事件处理器
│       ├── components/
│       │   ├── Layout.jsx          # 应用外壳(导航栏、侧边栏)
│       │   ├── ProtectedRoute.jsx  # 认证守卫
│       │   ├── ShareLinkDialog.jsx # 分享链接创建对话框
│       │   ├── AddToCollectionDialog.jsx
│       │   ├── CookieBanner.jsx
│       │   ├── LanguageSelector.jsx
│       │   ├── RequireEmailDialog.jsx
│       │   ├── UserAvatar.jsx
│       │   └── ui/                 # shadcn/ui 基础组件
│       ├── pages/
│       │   ├── Upload.jsx          # 上传页面
│       │   ├── Gallery.jsx         # 文件相册
│       │   ├── FileView.jsx        # 文件详情视图
│       │   ├── Collections.jsx     # 集合概览
│       │   ├── CollectionView.jsx  # 单个集合
│       │   ├── ShareView.jsx       # 公开分享链接页面
│       │   ├── Settings.jsx        # 用户设置
│       │   ├── Login.jsx
│       │   ├── Register.jsx
│       │   ├── ForgotPassword.jsx
│       │   ├── ResetPassword.jsx
│       │   ├── Install.jsx         # 初始安装
│       │   ├── PrivacyPolicy.jsx
│       │   ├── TermsOfService.jsx
│       │   └── admin/
│       │       ├── Dashboard.jsx   # 管理员首页
│       │       ├── Users.jsx       # 用户管理
│       │       ├── Files.jsx       # 文件管理
│       │       ├── AuditLog.jsx    # 审计日志视图
│       │       ├── SiteSettings.jsx# 运营者设置
│       │       └── Import.jsx      # XBackBone 导入
│       ├── i18n/
│       │   ├── index.js            # i18next 配置
│       │   └── locales/
│       │       ├── de.json
│       │       ├── en.json
│       │       ├── es.json
│       │       ├── fr.json
│       │       ├── it.json
│       │       ├── ja.json
│       │       ├── pt.json
│       │       └── zh.json
│       └── lib/
│           └── utils.js            # Tailwind 工具函数(cn())
│
├── scripts/
│   ├── setup-db.js
│   ├── mongo-init.js               # MongoDB 初始化脚本
│   ├── migrate-uploads-to-user-folders.js
│   └── generate-missing-thumbnails.js
│
├── e2e/                            # Playwright 测试
│   ├── admin.spec.js
│   ├── bulk-actions-fixes.spec.js
│   ├── gallery.spec.js
│   ├── sharelink.spec.js
│   ├── tags.spec.js
│   ├── upload.spec.js
│   ├── helpers.js
│   └── global-setup.js
│
├── uploads/                        # 文件上传(运行时)
│   ├── .thumbnails/
│   ├── .avatars/
│   └── .chunks/                    # 临时分块
│
└── docs/assets/                    # 截图和 Logo

5. 配置与环境变量

所有变量均从 .env 文件加载(通过 dotenv)。.env.example 文件包含完整模板。

必填字段

变量描述
SESSION_SECRET会话加密密钥 — 长随机字符串,例如 openssl rand -hex 32
MONGO_ROOT_PASSWORDMongoDB root 密码(仅 Docker Compose 需要)
MONGO_APP_PASSWORDMongoDB 应用用户密码

所有环境变量

变量默认值描述
PORT3000HTTP 服务器的 TCP 端口
MONGODB_URI_(由 Docker Compose 构建)_完整的 MongoDB 连接 URI
MONGO_ROOT_PASSWORDMongoDB root 密码
MONGO_APP_USERappuserMongoDB 应用用户名
MONGO_APP_PASSWORDMongoDB 应用用户密码
MONGO_DB_NAMEsharelyMongoDB 数据库名称
SESSION_SECRET必填 — 会话加密密钥
BASE_URLhttp://localhost:3000生成分享链接的公开基础 URL(末尾不含 /
SITE_NAMEsharelyOpen Graph 嵌入中的站点名称
MAX_FILE_SIZE_MB100标准上传的最大文件大小(MB)(分块上传独立支持最大 2GB)
ALLOW_REGISTRATIONtruefalse 禁用公开注册
SMTP_HOSTSMTP 服务器主机名;留空则禁用邮件功能
SMTP_PORT587SMTP 端口
SMTP_SECUREfalse隐式 TLS(端口 465)填 true,STARTTLS 填 false
SMTP_USERSMTP 用户名
SMTP_PASSSMTP 密码
SMTP_FROM_(SMTP_USER)_外发邮件的发件人地址
UPLOAD_DIR./uploads上传目录的绝对路径
NODE_ENVproduction 启用安全 Cookie

6. 数据模型

User(src/models/User.js

{
  username:                    String(3–32,唯一,字母数字 + _-)
  password:                    String(bcrypt,12 轮)
  role:                        'admin' | 'user'
  apiKey:                      String(遗留,迁移后为空)
  apiKeyHash:                  String(SHA-256,唯一,sparse)
  apiKeyPrefix:                String(明文的前 8 个字符)
  folderName:                  String(唯一,sparse,最大 64)
  avatarExt:                   String | null(.jpg/.png/.gif/.webp)
  embedMode:                   'embed' | 'raw'
  isActive:                    Boolean
  email:                       String(小写,唯一,sparse)
  emailVerified:               Boolean
  emailVerificationToken:      String | null(明文的 SHA-256 哈希)
  emailVerificationExpires:    Date | null
  passwordResetToken:          String | null(SHA-256 哈希)
  passwordResetExpires:        Date | null
  language:                    'en'|'de'|'fr'|'es'|'it'|'pt'|'ja'|'zh'
  predefinedTags:              [String](最多 100 个标签 × 50 个字符)
  createdAt:                   Date
}

重要方法:

File(src/models/File.js

{
  shortId:      String(8 个十六进制字符:6 位时间戳 + 2 位随机,唯一)
  deleteToken:  String(32 个十六进制字符,唯一)
  originalName: String(已净化)
  storedName:   String(相对路径:"folderName/8hex.ext")
  mimeType:     String
  size:         Number(字节)
  uploader:     ObjectId → User
  views:        Number
  tags:         [String](最多 20 × 50 个字符)
  createdAt:    Date
}

虚拟属性:

Short ID 算法:

shortId = hex(seconds_since_2024-01-01, 6 chars) + randomBytes(2).hex()

Collection(src/models/Collection.js

{
  shortId:     String(8 位十六进制,唯一)
  name:        String(最大 100)
  description: String(最大 500)
  owner:       ObjectId → User
  files:       [ObjectId → File]
  password:    String | null(bcrypt)
  expiresAt:   Date | null
  createdAt:   Date
}
过期的集合将在到期后 7 天由 MongoDB TTL 索引自动删除。

ShareLink(src/models/ShareLink.js

{
  token:         String(32 位十六进制,唯一)
  file:          ObjectId → File
  createdBy:     ObjectId → User
  label:         String(最大 100)
  password:      String | null(bcrypt)
  expiresAt:     Date | null
  downloadLimit: Number(-1 = 无限制)
  downloadCount: Number
  createdAt:     Date
}
与集合相同:通过 TTL 索引在过期后享有 7 天宽限期。

SiteSettings(src/models/SiteSettings.js

单例文档(_id: 'singleton'):

{
  operatorName:        String
  operatorAddress:     String
  operatorEmail:       String
  cloudflareAnalytics: Boolean
  fileRetentionDays:   Number(0 = 禁用)
  encryptionAtRest:    Boolean
  sessionDurationDays: Number(默认:7)
  allowRegistration:   Boolean
}

AuditLog(src/models/AuditLog.js

{
  timestamp: Date(TTL 索引:90 天)
  userId:    ObjectId → User | null
  username:  String | null
  action:    String
  ip:        String | null
  meta:      Mixed(特定于操作的元数据)
}

7. REST API

基础 URL:/api

所有 JSON 端点均返回 Content-Type: application/json。错误格式:{ "error": "消息" }


认证(/api/auth

方法路径认证描述
GET/me会话当前登录用户
POST/login登录(限流:10 次/15 分钟)
POST/register注册(限流,第一个用户成为管理员)
POST/logout退出登录
GET/smtp-enabled检查 SMTP 是否已配置
GET/verify-email?token=验证邮箱地址
GET/verify-reset-token?token=验证重置令牌
POST/forgot-password发送密码重置邮件(限流:5 次/小时)
POST/reset-password设置新密码

登录请求:

POST /api/auth/login
{ "username": "max", "password": "meinPasswort123" }

登录响应:

{
  "user": {
    "id": "...", "username": "max", "role": "user",
    "avatarUrl": null, "email": "[email protected]",
    "emailVerified": true, "language": "de"
  }
}

文件上传

方法路径认证描述
POST/uploadAPI 密钥ShareX 上传(app.js 中的遗留端点)
POST/api/uploadAPI 密钥ShareX/API 上传(字段:file
POST/api/web-upload会话Web 上传(字段:files[],最多 500)
POST/api/chunk/init会话初始化分块上传
POST/api/chunk/:uploadId会话上传单个分块(字段:chunk
POST/api/chunk/:uploadId/complete会话合并分块
DELETE/api/chunk/:uploadId会话取消上传并清理

API 上传响应:

{
  "url": "https://example.com/f/a1b2c3d4",
  "raw": "https://example.com/f/a1b2c3d4/raw",
  "delete_url": "https://example.com/api/delete/a1b2c3d4",
  "short_id": "a1b2c3d4",
  "filename": "screenshot.png",
  "size": 102400
}

分块上传流程

1. POST /api/chunk/init{ uploadId: "32hex" }

2. POST /api/chunk/:uploadId(Body:chunkIndex=N,文件:chunk)→ { received: N }

并行上传 3–5 个分块

3. POST /api/chunk/:uploadId/complete{ files: [fileObject] }


文件管理

方法路径认证描述
GET/api/gallery会话自己的文件(管理员:所有文件),分页(24 个/页),筛选:qtypetagpage
GET/api/file/:shortId文件元数据(递增访问计数)
PATCH/api/file/:shortId会话更新标签/名称
DELETE/api/file/:shortId会话删除文件
DELETE/api/delete/:shortIdAPI 密钥删除文件(API 密钥认证)
POST/api/files/bulk会话批量操作:deletetagremoveTagaddToCollectionmoveToCollection
GET/api/tags会话用户的所有标签建议

相册查询参数:


用户设置

方法路径认证描述
GET/api/my-key会话显示 API 密钥前缀
POST/api/regen-key会话重新生成 API 密钥
GET/api/sharex-config会话下载 ShareX .sxcu(重新生成密钥)
PATCH/api/user/username会话修改用户名(需要密码)
PATCH/api/user/password会话修改密码
PATCH/api/user/email会话修改邮箱(发送验证邮件)
PATCH/api/user/language会话设置界面语言
PATCH/api/user/embed-mode会话设置嵌入模式(embed/raw
POST/api/user/resend-verification会话重新发送验证邮件
GET/api/user/export会话数据导出(GDPR 第 20 条),JSON 格式
DELETE/api/user/account会话删除账户(GDPR 第 17 条,需要密码)
GET/api/user/predefined-tags会话获取预定义标签
PATCH/api/user/predefined-tags会话更新预定义标签
POST/api/user/avatar会话上传头像(最大 2MB,JPEG/PNG/GIF/WebP)
DELETE/api/user/avatar会话删除头像
GET/api/user/avatar/:userId提供头像服务

分享链接

方法路径认证描述
GET/api/file/:shortId/share-links会话(所有者/管理员)文件的所有分享链接
POST/api/file/:shortId/share-links会话(所有者/管理员)创建分享链接
DELETE/api/share-links/:token会话(所有者/创建者/管理员)删除分享链接
GET/api/share-links/:token分享链接元数据(公开)
POST/api/share-links/:token/verify验证分享链接密码

创建分享链接:

POST /api/file/a1b2c3d4/share-links
{
  "label": "给同事用",
  "password": "密码",
  "expiresAt": "2025-12-31T23:59:59Z",
  "downloadLimit": 10
}

集合

方法路径认证描述
GET/api/collections会话自己的集合(管理员:所有集合)
POST/api/collections会话创建集合
GET/api/collections/:id查看集合(公开,设置了密码则需要输入)
PATCH/api/collections/:id会话(所有者/管理员)更新集合
DELETE/api/collections/:id会话(所有者/管理员)删除集合
POST/api/collections/:id/files会话(所有者/管理员)向集合添加文件
DELETE/api/collections/:id/files/:fileShortId会话(所有者/管理员)从集合中移除文件
POST/api/collections/:id/verify验证集合密码

管理员端点

方法路径认证描述
GET/api/admin/stats管理员仪表板统计信息
GET/api/admin/users管理员所有用户(含文件数量)
POST/api/admin/users管理员创建用户
PATCH/api/admin/users/:id/toggle管理员启用/禁用用户
PATCH/api/admin/users/:id/role管理员修改用户角色
DELETE/api/admin/users/:id管理员删除用户
POST/api/admin/users/:id/regen-key管理员重新生成 API 密钥
PATCH/api/admin/users/:id/password管理员设置密码
PATCH/api/admin/users/:id/folder管理员修改文件夹名称(移动文件)
GET/api/admin/files管理员所有文件,分页(30 个/页)
GET/api/admin/site-settings管理员读取运营者设置
PATCH/api/admin/site-settings管理员更新运营者设置
GET/api/admin/audit-log管理员分页审计日志(50 条/页)
GET/api/admin/audit-log/export管理员下载审计日志 CSV
GET/api/site-settings运营者公开信息(用于隐私政策页面)

站点设置(公开)

GET /api/site-settings
{
  "operatorName": "Musterfirma GmbH",
  "operatorAddress": "Musterstr. 1, 12345 Musterstadt",
  "operatorEmail": "[email protected]",
  "cloudflareAnalytics": false,
  "fileRetentionDays": 365,
  "encryptionAtRest": false,
  "sessionDurationDays": 7
}

8. WebSocket API

连接: wss://example.com/ws(仅限已登录用户,需要会话 Cookie)

协议

客户端 → 服务器(请求):

{ "id": "req-abc123", "action": "file:list", "payload": { "type": "image", "page": 1 } }

服务器 → 客户端(响应):

{ "id": "req-abc123", "data": { ... } }

服务器 → 客户端(错误):

{ "id": "req-abc123", "error": "Forbidden", "status": 403 }

服务器 → 客户端(广播):

{ "event": "file:uploaded", "data": { "shortId": "a1b2c3d4", "uploaderId": "..." } }

可用操作

操作认证描述
site-settings:get站点公开设置
auth:me用户当前用户数据
file:get用户文件详情(递增访问次数)
file:list用户带筛选/分页的文件列表
file:delete用户删除文件
user:get-key用户API 密钥前缀
user:regen-key用户重新生成 API 密钥
user:change-password用户修改密码
user:change-username用户修改用户名
user:change-email用户修改邮箱
user:change-language用户设置语言
user:change-embed-mode用户设置嵌入模式
user:resend-verification用户重新发送验证邮件
user:export用户数据导出
user:delete-account用户删除账户
admin:stats管理员仪表板统计信息
admin:settings:get管理员读取站点设置
admin:settings:update管理员更新站点设置
admin:users:list管理员所有用户
admin:users:create管理员创建用户
admin:users:toggle管理员启用/禁用用户
admin:users:role管理员修改用户角色
admin:users:delete管理员删除用户
admin:users:regen-key管理员重新生成 API 密钥
admin:users:password管理员设置密码
admin:users:folder管理员修改文件夹名称
admin:files:list管理员所有文件
admin:audit-log:list管理员分页审计日志

广播事件

事件接收者负载
file:uploaded上传者{ shortId, uploaderId }
file:deleted文件所有者{ shortId, uploaderId }
file:view所有人{ shortId, views }
user:created管理员{ id, username, role, ... }
user:deleted管理员{ id }
user:updated管理员{ id, ...已更改字段 }
audit:log管理员完整的 AuditLog 对象
settings:updated管理员更新后的 SiteSettings 对象
stats:invalidate管理员{}(触发统计刷新)

9. 文件服务路由

/f/:shortId — 文件查看器

- embedMode = 'embed':带重定向的 OG HTML

- embedMode = 'raw' + 图片/视频/音频:HTTP 302 → /f/:shortId/raw

/f/:shortId/raw — 直接访问

以 HTTP 响应的形式提供文件,支持 Range 请求(206 Partial Content)。图片/视频/音频:Content-Disposition: inline,其他:attachment

/f/:shortId/download — 强制下载

/raw 相同,但始终使用 Content-Disposition: attachment

/f/:shortId/thumb — 缩略图

返回 JPEG 缩略图(适用于视频和 PDF)。Cache-Control: public, max-age=86400

/f/:shortId/delete/:token — ShareX 删除

通过唯一删除令牌无需会话即可删除文件。

/s/:token — 分享链接文件服务(src/routes/shares.js

验证密码(通过会话标志)、过期日期和下载限制,然后提供文件。


10. 身份验证与安全

会话认证

API 密钥认证

中间件链(src/middleware/auth.js

requireLogin    → 检查 req.session.user → 未登录返回 401
requireAdmin    → 类似 requireLogin,额外要求 role === 'admin' → 403
requireApiKey   → 检查 Authorization 请求头或 req.body.token

CSRF 防护

app.js 中的 requireSameOrigin() 对所有 API 路由比较 Origin 请求头与 Host 请求头。配合 sameSite: 'strict' Cookie 共同防护。

内容安全策略

default-src 'self';
script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
media-src 'self' blob:;
connect-src 'self' https://cloudflareinsights.com;
frame-ancestors 'self';

安全响应头

限流

端点限制时间窗口
上传(/upload/api/upload/api/web-upload60 次请求15 分钟
认证(/api/auth/login/register 等)10 次请求15 分钟
密码重置5 次请求1 小时

文件黑名单

以下 MIME 类型和扩展名在上传时将被拒绝:

被阻止的 MIME 类型: application/x-executableapplication/x-shapplication/x-cshapplication/x-bat

被阻止的扩展名: .bat.cmd.com.ps1.psm1.psd1.sh.bash.csh.zsh.fish.vbs.vbe.jse.scr.pif.application.gadget.hta.php.php3–5.phtml.asp.aspx.jsp.jspx.cfm

路径遍历防护

所有通过 resolveUploadPath() 的文件访问都会验证解析路径是否位于 UPLOAD_DIR 内。


11. 上传系统

标准上传(Multer)

分块上传(>250 MB)

前端客户端会自动为大文件切换到分块模式。

服务器端目录结构:

uploads/.chunks/{uploadId}/
  meta.json          { filename, mimeType, totalSize, totalChunks, userId, createdAt }
  chunk-0
  chunk-1
  ...
  chunk-N

分块大小: 10–20 MB(向后兼容最多接受 51 MB)

并发数: 3–5 个分块同时上传

合并方式: 基于流(不在 RAM 中完全加载)

头像上传


12. 缩略图生成

缩略图在上传后异步生成(.catch(() => {}) — 错误会被静默忽略)。

视频缩略图(ffmpeg)

ffmpeg -y -i <file> -ss 00:00:01 -vframes 1 \
  -vf "scale=320:320:force_original_aspect_ratio=increase,crop=320:320" \
  -q:v 3 uploads/.thumbnails/<shortId>.jpg

PDF 缩略图(Ghostscript)

gs -dNOPAUSE -dBATCH -dSAFER \
  -sDEVICE=jpeg -dFirstPage=1 -dLastPage=1 \
  -r72 -dJPEGQ=85 \
  -sOutputFile=uploads/.thumbnails/<shortId>.jpg \
  <file>

超时: 每次缩略图生成 30 秒

回退: 如果 ffmpeg/ghostscript 不可用,生成将静默跳过。

补充生成脚本

npm run migrate:thumbnails
# 或在容器中:
docker exec -it <container> npm run migrate:thumbnails

13. 邮件与 SMTP

配置

设置 SMTP_HOST 后 SMTP 即被激活。mailer.isConfigured() 检查该值。

邮件模板

所有邮件均以用户的语言发送(8 种语言)。模板内嵌于 src/utils/mailer.js

发送的邮件类型:

类型触发器令牌有效期
邮箱验证注册、修改邮箱24 小时
密码重置POST /api/auth/forgot-password1 小时

令牌安全


14. 国际化

库: i18next + react-i18next + i18next-browser-languagedetector

支持的语言:

代码语言
enEnglish
deDeutsch
frFrançais
esEspañol
itItaliano
ptPortuguês
ja日本語
zh中文

语言选择:

1. 浏览器语言检测(自动)

2. 数据库中的用户偏好(user.language

3. 通过 PATCH /api/user/language 持久化

翻译文件:client/src/i18n/locales/{code}.json


15. 前端(React SPA)

路由配置(client/src/App.jsx

路由组件认证
/重定向 → /gallery
/auth/loginLogin.jsx
/auth/registerRegister.jsx
/auth/forgot-passwordForgotPassword.jsx
/auth/reset-passwordResetPassword.jsx
/installInstall.jsx
/uploadUpload.jsx
/galleryGallery.jsx
/f/:shortIdFileView.jsx
/collectionsCollections.jsx
/c/:idCollectionView.jsx否(公开)
/s/:tokenShareView.jsx否(公开)
/settingsSettings.jsx
/adminDashboard.jsx管理员
/admin/usersUsers.jsx管理员
/admin/filesFiles.jsx管理员
/admin/audit-logAuditLog.jsx管理员
/admin/site-settingsSiteSettings.jsx管理员
/admin/importImport.jsx管理员
/privacyPrivacyPolicy.jsx
/termsTermsOfService.jsx

认证上下文(client/src/context/AuthContext.jsx

已登录用户的全局状态。应用启动时通过 GET /api/auth/me 初始化。

WebSocket Hook(client/src/hooks/useWebSocket.js

管理持久化的 WS 连接。提供 sendMessage() 和事件处理器注册。连接断开时的重连逻辑。

UI 组件

基于 shadcn/ui(Radix UI Primitives + Tailwind CSS):


16. 管理后台

管理后台(/admin/*)仅限 role: 'admin' 的用户访问。

仪表板(/admin

用户管理(/admin/users

文件管理(/admin/files

审计日志(/admin/audit-log

站点设置(/admin/site-settings

XBackBone 导入(/admin/import


17. GDPR / 隐私保护

功能GDPR 条款
隐私政策(可配置)第 13/14 条 – 透明度
服务条款页面(可配置)第 13/14 条 – 透明度
数据导出(含 URL 的 JSON)第 20 条 – 数据可携带性
账户自删除(文件+数据)第 17 条 – 删除权
审计日志(通过 MongoDB TTL 保留 90 天)第 5(2) 条 – 问责制
审计日志 CSV 导出第 5(2) 条 – 问责制
可配置的文件保留期第 5(1)(e) 条 – 存储限制
API 密钥以 SHA-256 哈希形式存储第 32 条 – 安全性
密码以 bcrypt(12 轮)形式存储第 32 条 – 安全性
Cloudflare Analytics 的 Cookie 同意第 13 条 – 透明度
账户删除时匿名化第 17 条 – 删除权

GDPR 删除流程

删除账户时(user:delete-account / DELETE /api/user/account):

1. 用户的所有文件从磁盘删除

2. 缩略图被删除

3. 头像被删除

4. 审计日志条目被匿名化(username: '[deleted]'ip: nulluserId: null

5. 用户文档被删除

6. 会话被销毁


18. 后台任务

保留期清理(src/jobs/retentionCleanup.js

MongoDB TTL 索引(自动)

集合TTL触发字段
AuditLog90 天timestamp
CollectionexpiresAt 后 7 天expiresAt
ShareLinkexpiresAt 后 7 天expiresAt

19. 部署

Docker(推荐)

git clone https://github.com/Christianoooooo/sharely.git
cd sharely
cp .env.example .env
# 编辑 .env(SESSION_SECRET、MONGO 密码、BASE_URL)
docker compose up -d

服务:

数据卷:

健康检查: 应用检查 / 是否返回 HTTP 200,MongoDB 检查 db.adminCommand('ping')

Nginx 反向代理

server {
    listen 443 ssl;
    server_name files.example.com;

    client_max_body_size 2100M;  # 至少要大于最大分块大小加缓冲

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
.env 中的 BASE_URL 必须与公开域名一致。

首次启动

第一个注册的用户将自动获得 admin 角色(User.countDocuments() === 0)。


20. 开发环境

前提条件

搭建环境

# 后端
npm install
cp .env.example .env
# 编辑 .env

# 前端
cd client
npm install

启动

# 后端(端口 3000,使用 nodemon)
npm run dev

# 前端(端口 5173,另开终端)
cd client
npm run dev

Vite 开发服务器会自动将 API 请求代理到 localhost:3000

构建

npm run build   # 构建 client/dist/,后端不变
npm start       # 启动生产环境 Express 服务器

21. 迁移与脚本

自动迁移(每次启动时)

这些迁移在 app.js 启动时运行,具有幂等性:

迁移文件功能
用户文件夹迁移src/migrations/migrateUserFolders.js将文件从 uploads/ 移动到 uploads/{folderName}/
API 密钥哈希迁移src/migrations/migrateApiKeyHashes.js将明文 API 密钥转换为 SHA-256 哈希

手动脚本

# 为已有文件生成缩略图
npm run migrate:thumbnails
# 或:
node scripts/generate-missing-thumbnails.js

# 将上传文件移动到用户文件夹(手动)
npm run migrate:user-folders
# 或:
node scripts/migrate-uploads-to-user-folders.js

数据库初始化(Docker)

scripts/mongo-init.js 在 MongoDB 容器首次启动时执行,以正确权限创建应用用户。


22. 端到端测试

框架: Playwright(@playwright/test

测试文件

文件测试套件
e2e/upload.spec.js上传流程
e2e/gallery.spec.js相册与文件管理
e2e/admin.spec.js管理后台
e2e/sharelink.spec.js分享链接的创建与使用
e2e/tags.spec.js标签管理
e2e/bulk-actions-fixes.spec.js批量操作

运行测试

# 所有测试
npm run test:e2e

# 带 UI 界面
npm run test:e2e:ui

Playwright 配置: playwright.config.js

全局初始化: e2e/global-setup.js(创建测试用户、管理员等)

辅助函数: e2e/helpers.js(共享辅助函数)


附录:审计日志操作列表

操作触发器
login登录成功
logout退出登录
register注册
upload文件上传
delete_file文件已删除
delete_account账户已删除
change_password密码已修改
change_username用户名已修改
change_email邮箱已修改
verify_email邮箱已验证
forgot_password密码重置请求
reset_password密码已重置
regen_api_keyAPI 密钥已重新生成
sharex_configShareX 配置已下载
export_data数据导出
admin_create_user管理员:用户已创建
admin_delete_user管理员:用户已删除
admin_toggle_user管理员:用户已启用/禁用
admin_change_role管理员:用户角色已修改
admin_change_password管理员:密码已设置
admin_regen_key管理员:API 密钥已重新生成

*本文档由 sharely v1.0.0 源代码生成*