# 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 加密传输 - 图片浏览/下载功能 - 管理后台