383 lines
11 KiB
Markdown
383 lines
11 KiB
Markdown
# NAS图片上传系统 - 开发文档
|
||
|
||
> **版本**:v0.3
|
||
> **日期**:2026-04-17
|
||
> **状态**:需求已确认,待开发
|
||
|
||
---
|
||
|
||
## 1. 项目概述
|
||
|
||
### 1.1 项目背景
|
||
|
||
需要搭建一个简单的Web应用,允许用户通过手机或电脑访问网站,将图片上传到本地NAS存储中。网站部署在公网实例上,通过内网穿透转发请求到本地NAS。
|
||
|
||
### 1.2 核心需求
|
||
|
||
- 用户通过手机/电脑浏览器访问网站,选择目标文件夹后上传图片
|
||
- 支持多选图片上传,带上传进度条显示
|
||
- 上传完成后显示成功上传的图片列表,方便用户确认是否全部上传成功
|
||
- 文件夹结构:训练营N期 → 25个班级文件夹,用户只能选择不能创建
|
||
- 文件夹自动创建:首次上传时若目标文件夹不存在则自动创建
|
||
- 每个训练营文件夹下有一个MD文档,统计图片数量
|
||
- 无需登录,公开使用(后续考虑加密码保护)
|
||
- 仅上传功能,不提供图片浏览/下载
|
||
- 图片主要为屏幕截图
|
||
|
||
### 1.3 网络架构
|
||
|
||
公网实例通过内网穿透(frp)将请求转发到本地NAS上的服务。服务端直接运行在群晖NAS上。
|
||
|
||
```
|
||
用户手机/电脑 → 公网实例(frp) → 内网穿透 → NAS服务端
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 技术方案
|
||
|
||
### 2.1 技术栈
|
||
|
||
| 层次 | 技术选型 | 说明 |
|
||
|------|----------|------|
|
||
| 后端框架 | Python + Flask | 轻量、开发快、文件操作方便,NAS环境通常支持Python |
|
||
| 前端 | HTML + CSS + JavaScript | 纯静态页面,无需前端构建工具,部署简单 |
|
||
| 图片上传 | XMLHttpRequest | 原生支持上传进度事件 |
|
||
| 文件存储 | NAS本地文件系统 | 直接读写NAS磁盘目录 |
|
||
| 内网穿透 | frp | 将公网请求转发到内网服务 |
|
||
| 配置管理 | .env 文件 | 部署时填写NAS路径、端口等配置 |
|
||
|
||
---
|
||
|
||
## 3. 系统架构
|
||
|
||
### 3.1 整体架构
|
||
|
||
```
|
||
┬── 用户手机/电脑(浏览器)
|
||
│
|
||
├── 公网实例(frp server)
|
||
│
|
||
├── frp client → 内网穿透
|
||
│
|
||
└── 群晖 NAS(Flask + 静态文件)
|
||
├── /api/folders 获取文件夹列表
|
||
├── /api/upload 上传图片
|
||
├── /static/ 前端静态资源
|
||
└── {NAS_DATA_PATH}/ NAS存储目录(.env配置)
|
||
```
|
||
|
||
### 3.2 NAS目录结构
|
||
|
||
```
|
||
{NAS_DATA_PATH}/ ← 通过 .env 配置
|
||
├── 训练营13期/
|
||
│ ├── 统计.md ← 图片数量统计文档
|
||
│ ├── 1班/
|
||
│ ├── 2班/
|
||
│ ├── ...
|
||
│ └── 25班/
|
||
├── 训练营14期/
|
||
│ ├── 统计.md
|
||
│ ├── 1班/
|
||
│ ├── ...
|
||
│ └── 25班/
|
||
└── ...
|
||
```
|
||
|
||
### 3.3 服务端项目结构
|
||
|
||
```
|
||
nas-uploader/
|
||
├── app.py ← Flask主应用
|
||
├── config.py ← 读取 .env 配置
|
||
├── .env ← 部署配置(NAS路径、端口等)
|
||
├── .env.example ← 配置示例文件
|
||
├── requirements.txt ← Python依赖
|
||
├── static/
|
||
│ ├── css/style.css ← 样式文件
|
||
│ ├── js/app.js ← 前端逻辑
|
||
│ └── img/ ← 图标等静态资源
|
||
├── templates/
|
||
│ └── index.html ← 主页面
|
||
└── start.sh ← 启动脚本
|
||
```
|
||
|
||
### 3.4 .env 配置文件
|
||
|
||
```env
|
||
# NAS图片存储根目录(部署时修改为实际路径)
|
||
NAS_DATA_PATH=/volume1/nas_data
|
||
|
||
# 服务端口
|
||
PORT=5000
|
||
|
||
# 允许的图片格式(小写,逗号分隔)
|
||
ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,webp
|
||
|
||
# 单次上传最大文件数量
|
||
MAX_FILES_COUNT=100
|
||
|
||
# 单张图片最大大小(MB)
|
||
MAX_FILE_SIZE_MB=20
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 功能设计
|
||
|
||
### 4.1 文件夹选择功能
|
||
|
||
#### 4.1.1 文件夹获取规则
|
||
|
||
- 后端扫描NAS根目录(`NAS_DATA_PATH`),读取所有"训练营*期"格式的文件夹
|
||
- 每个训练营文件夹下,读取所有"*班"格式的子文件夹
|
||
- 前端以两级联动选择器展示:第一级选训练营,第二级选班级
|
||
- 文件夹列表按名称排序(数字自然排序)
|
||
- **不显示**各班级已有图片数量
|
||
|
||
#### 4.1.2 文件夹自动创建
|
||
|
||
- 当用户选择某个班级并上传图片时,若对应的文件夹不存在,则自动创建
|
||
- 创建逻辑:检查训练营文件夹 → 检查班级文件夹 → 不存在则创建
|
||
- 同时创建对应的统计.md文档(若训练营文件夹是新建的)
|
||
- **新的"训练营N期"文件夹由管理员在NAS上手动创建**,用户不能通过网页创建新训练营
|
||
|
||
### 4.2 图片上传功能
|
||
|
||
#### 4.2.1 上传流程
|
||
|
||
```
|
||
用户选择文件夹 → 点击"选择图片"按钮 → 多选图片 → 点击"开始上传"
|
||
```
|
||
|
||
- 支持的图片格式:JPG、JPEG、PNG、GIF、WEBP(不支持HEIC)
|
||
- 单次上传数量限制:最多100张
|
||
- 单张大小限制:20MB
|
||
- 采用逐张上传策略,每张图片独立显示进度,同时显示总体进度
|
||
|
||
#### 4.2.2 图片命名规则
|
||
|
||
上传后图片按以下格式重命名:
|
||
|
||
```
|
||
训练营{n}期-{x}班-{时间戳}-{随机数}.{扩展名}
|
||
```
|
||
|
||
示例:
|
||
```
|
||
训练营13期-1班-20260417143022-a3f8b2.png
|
||
训练营13期-1班-20260417143022-7c2e91.jpg
|
||
```
|
||
|
||
- `{n}`:训练营期数
|
||
- `{x}`:班级编号
|
||
- `{时间戳}`:精确到秒,格式 `YYYYMMDDHHmmss`
|
||
- `{随机数}`:6位十六进制随机字符串
|
||
- `{扩展名}`:保留原始文件的扩展名
|
||
- 若文件名已存在(极低概率),自动在末尾追加序号:`-a3f8b2-1.png`
|
||
|
||
#### 4.2.3 上传进度显示
|
||
|
||
- 总体进度条:显示已上传完成的数量 / 总数量
|
||
- 单张进度条:每张图片的上传百分比
|
||
- 状态标识:等待中 / 上传中 / 成功 / 失败
|
||
|
||
#### 4.2.4 上传完成反馈
|
||
|
||
- 上传成功后,页面底部显示"已成功上传"图片缩略图列表
|
||
- 每张图片显示:缩略图 + 新文件名 + 上传状态
|
||
- 失败的图片明确标红,显示失败原因,支持"重试"操作
|
||
- 显示摘要信息:"成功上传15张,失败 1张"
|
||
|
||
### 4.3 图片统计功能
|
||
|
||
#### 4.3.1 统计.md格式
|
||
|
||
每个训练营文件夹下的统计.md内容格式如下:
|
||
|
||
```markdown
|
||
# 训练营XX期 图片统计
|
||
|
||
更新时间:2026-04-17 14:30:00
|
||
图片总数:156
|
||
|
||
| 班级 | 图片数量 |
|
||
|------|----------|
|
||
| 1班 | 12 |
|
||
| 2班 | 8 |
|
||
| ... | ... |
|
||
| 25班 | 6 |
|
||
```
|
||
|
||
#### 4.3.2 统计更新触发
|
||
|
||
- 每次上传成功后,自动更新对应训练营的统计.md
|
||
- 统计方式:扫描训练营文件夹及其所有子目录,统计图片文件数量
|
||
- 图片文件判定:根据文件扩展名(jpg、jpeg、png、gif、webp)
|
||
- 更新时间记录在文档头部
|
||
|
||
---
|
||
|
||
## 5. API设计
|
||
|
||
### 5.1 获取文件夹列表
|
||
|
||
**GET /api/folders**
|
||
|
||
返回所有训练营及其下的班级文件夹。
|
||
|
||
**响应示例:**
|
||
|
||
```json
|
||
{
|
||
"folders": [
|
||
{
|
||
"name": "训练营13期",
|
||
"classes": ["1班", "2班", "3班", "4班", "5班", "...", "25班"]
|
||
},
|
||
{
|
||
"name": "训练营14期",
|
||
"classes": ["1班", "3班"]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 5.2 上传图片
|
||
|
||
**POST /api/upload**
|
||
|
||
接受multipart/form-data格式的图片上传。
|
||
|
||
**请求参数:**
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| folder | string | 是 | 训练营名称,如"训练营13期" |
|
||
| class | string | 是 | 班级名称,如"1班" |
|
||
| files | File[] | 是 | 图片文件数组,支持多文件 |
|
||
|
||
**响应示例:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"uploaded": [
|
||
{ "filename": "训练营13期-1班-20260417143022-a3f8b2.png", "status": "success" },
|
||
{ "filename": "训练营13期-1班-20260417143023-7c2e91.jpg", "status": "success" }
|
||
],
|
||
"failed": [
|
||
{ "filename": "photo3.bmp", "status": "failed", "reason": "文件格式不支持" }
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 前端页面设计
|
||
|
||
### 6.1 页面布局
|
||
|
||
页面采用简洁的单页设计,适配手机和电脑访问(响应式布局)。
|
||
|
||
页面从上到下分为三个区域:
|
||
|
||
#### 6.1.1 顶部:文件夹选择区
|
||
|
||
- 第一行:下拉选择"训练营"(如"训练营13期")
|
||
- 第二行:下拉选择"班级"(如"1班")
|
||
- 班级列表根据选中的训练营动态加载
|
||
|
||
#### 6.1.2 中部:图片选择和上传区
|
||
|
||
- "选择图片"按钮:触发文件选择器,支持多选
|
||
- 已选择的图片列表:显示缩略图、文件名、可删除
|
||
- "开始上传"按钮:触发上传操作
|
||
- 进度条:总体进度 + 单张进度
|
||
|
||
#### 6.1.3 底部:上传结果区
|
||
|
||
- 显示成功上传的图片缩略图网格
|
||
- 失败的图片显示红色标记和"重试"按钮
|
||
- 显示摘要信息:"成功上传15张,失败 1张"
|
||
|
||
### 6.2 移动端适配
|
||
|
||
- 采用响应式设计,自动适配手机和电脑屏幕
|
||
- 手机端优化:按钮大小适中触摸操作,图片缩略图网格自动调整列数
|
||
- 支持手机原生图片选择器(accept="image/*")
|
||
|
||
---
|
||
|
||
## 7. 部署方案
|
||
|
||
### 7.1 frp内网穿透配置
|
||
|
||
详见 [frp内网穿透_原理与部署指南.md](./frp内网穿透_原理与部署指南.md)
|
||
|
||
**frpc.toml(NAS侧)关键配置:**
|
||
|
||
```toml
|
||
serverAddr = "你的公网IP"
|
||
serverPort = 7000
|
||
auth.token = "你的认证密码"
|
||
|
||
[[proxies]]
|
||
name = "nas-uploader"
|
||
type = "tcp"
|
||
localIP = "127.0.0.1"
|
||
localPort = 5000
|
||
remotePort = 8080
|
||
```
|
||
|
||
### 7.2 服务启动
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# start.sh
|
||
cd /path/to/nas-uploader
|
||
source venv/bin/activate # 可选:使用虚拟环境
|
||
python app.py
|
||
```
|
||
|
||
### 7.3 部署步骤
|
||
|
||
1. 将项目文件上传到群晖NAS
|
||
2. 复制 `.env.example` 为 `.env`,修改 `NAS_DATA_PATH` 为实际的共享文件夹路径
|
||
3. 安装Python依赖:`pip install -r requirements.txt`
|
||
4. 启动服务:`bash start.sh`
|
||
5. 配置frpc客户端连接到公网服务器
|
||
6. 浏览器访问 `http://公网IP:8080` 验证
|
||
|
||
---
|
||
|
||
## 8. 开发计划
|
||
|
||
| 阶段 | 内容 | 预估工时 |
|
||
|------|------|----------|
|
||
| 第一阶段 | 后端基础:Flask服务 + .env配置 + API + 文件操作 | 2-3小时 |
|
||
| 第二阶段 | 前端页面:文件夹选择 + 图片上传 + 进度条 + 结果展示 | 2-3小时 |
|
||
| 第三阶段 | 统计功能:自动更新统计.md | 1小时 |
|
||
| 第四阶段 | 优化:移动端适配 + 异常处理 + 测试 | 1-2小时 |
|
||
| 第五阶段 | 部署:frp配置 + 群晖NAS部署 + 服务上线 | 0.5小时 |
|
||
|
||
---
|
||
|
||
## 9. 风险与注意事项
|
||
|
||
- **并发上传**:多用户同时上传到同一文件夹时,文件名含时间戳+随机数,冲突概率极低;若冲突则自动追加序号
|
||
- **存储空间**:需关注NAS磁盘空间,建议在上传前检查剩余空间
|
||
- **网络稳定性**:内网穿透可能因网络波动导致上传中断,需支持失败重试
|
||
- **安全性**:当前公开访问无需登录,后续考虑加密码保护;后端必须验证文件类型防止恶意上传
|
||
- **文件类型验证**:后端严格校验文件扩展名和MIME类型,防止上传非图片文件
|
||
|
||
---
|
||
|
||
## 10. 后续规划(暂不实现)
|
||
|
||
- 访问密码保护
|
||
- HTTPS 加密传输
|
||
- 图片浏览/下载功能
|
||
- 管理后台
|