11 KiB
11 KiB
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 配置文件
# 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内容格式如下:
# 训练营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
返回所有训练营及其下的班级文件夹。
响应示例:
{
"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[] | 是 | 图片文件数组,支持多文件 |
响应示例:
{
"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内网穿透配置
frpc.toml(NAS侧)关键配置:
serverAddr = "你的公网IP"
serverPort = 7000
auth.token = "你的认证密码"
[[proxies]]
name = "nas-uploader"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5000
remotePort = 8080
7.2 服务启动
#!/bin/bash
# start.sh
cd /path/to/nas-uploader
source venv/bin/activate # 可选:使用虚拟环境
python app.py
7.3 部署步骤
- 将项目文件上传到群晖NAS
- 复制
.env.example为.env,修改NAS_DATA_PATH为实际的共享文件夹路径 - 安装Python依赖:
pip install -r requirements.txt - 启动服务:
bash start.sh - 配置frpc客户端连接到公网服务器
- 浏览器访问
http://公网IP:8080验证
8. 开发计划
| 阶段 | 内容 | 预估工时 |
|---|---|---|
| 第一阶段 | 后端基础:Flask服务 + .env配置 + API + 文件操作 | 2-3小时 |
| 第二阶段 | 前端页面:文件夹选择 + 图片上传 + 进度条 + 结果展示 | 2-3小时 |
| 第三阶段 | 统计功能:自动更新统计.md | 1小时 |
| 第四阶段 | 优化:移动端适配 + 异常处理 + 测试 | 1-2小时 |
| 第五阶段 | 部署:frp配置 + 群晖NAS部署 + 服务上线 | 0.5小时 |
9. 风险与注意事项
- 并发上传:多用户同时上传到同一文件夹时,文件名含时间戳+随机数,冲突概率极低;若冲突则自动追加序号
- 存储空间:需关注NAS磁盘空间,建议在上传前检查剩余空间
- 网络稳定性:内网穿透可能因网络波动导致上传中断,需支持失败重试
- 安全性:当前公开访问无需登录,后续考虑加密码保护;后端必须验证文件类型防止恶意上传
- 文件类型验证:后端严格校验文件扩展名和MIME类型,防止上传非图片文件
10. 后续规划(暂不实现)
- 访问密码保护
- HTTPS 加密传输
- 图片浏览/下载功能
- 管理后台