微信小程序上传适配双环境:本地Java服务直存 + Nginx独立文件服务器转发 本文还有配套的精品资源点击获取简介微信小程序端支持两种文件上传路径切换无需改代码只需配置。一种走Java后端直接保存到本机磁盘由IndexController接收并落盘另一种对接独立Nginx文件服务器提供两个可选后端入口NginxController走标准MVC分层Controller→Service→Entity适合生产部署UploadController把全部逻辑压在控制器里方便开发阶段快速验证和调试。前端小程序已封装好上传调用、进度监听、错误提示等基础能力含完整项目结构——app.js、app.、project.config.、sitemap.、pages/index页面、utils工具类以及适配的wxss样式。后端基于Maven构建pom.xml已预置Spring Boot、Web、Lombok等依赖src/main下目录结构清晰可直接导入IDE运行。适用于中小项目在开发期用本地存储、上线后平滑切换至专用文件服务器的场景上传路径通过配置项控制不耦合业务逻辑。1. 项目概述为什么小程序上传要“双环境”做微信小程序开发的朋友大概率都踩过这个坑开发阶段文件上传到本地磁盘路径写死在IndexController里测试顺滑一到上线运维说“生产环境不允许后端直接写磁盘”得走独立文件服务器——你立刻懵了改 Controller重写上传逻辑前端也要同步改请求地址接口联调又来一轮更糟的是测试环境、预发环境、灰度环境可能各自用不同存储策略……最后代码里堆满if (env.equals(prod))维护成本飙升一个配置错图片全 404。这个项目解决的就是这种“部署即重构”的典型痛点。它不是教你怎么写一个上传接口而是提供一套可配置、零侵入、双路径并存、前后端解耦的上传适配方案。核心就一句话小程序前端完全不知道后端存哪只管调用统一上传方法后端通过配置开关自动路由到本地磁盘或 Nginx 文件服务器业务代码一行不改。关键词里的“微信小程序上传”“Java文件上传”“Nginx文件服务器”其实对应三个关键角色前端是发起方小程序 wx.uploadFile中间是调度方Spring Boot 后端终点是落地方本地磁盘 or Nginx 静态服务。而“双环境”的本质是把“存储决策权”从代码里抽出来交给配置层。比如application.yml里加一行upload: strategy: nginx # 可选 local | nginx再重启服务整个上传链路就无声切换了——前端不用发新版本数据库不用动字段连日志打印的路径前缀都自动变成/file/nginx/xxx.jpg或/file/local/xxx.jpg。这背后不是魔法而是对 Spring Boot 生命周期、Nginx 反向代理机制、小程序上传协议细节的扎实理解。比如很多人以为 Nginx 转发上传只是简单proxy_pass但实际必须处理Content-Length头透传、大文件超时、multipart boundary 解析兼容性又比如小程序上传要求后端返回{errno: 0, data: {url: xxx}}格式但本地存储返回的是相对路径Nginx 存储返回的是完整 HTTP 地址——这些差异全被封装在UploadResultBuilder工具类里业务 Controller 只需写return uploadService.handle(file)。适合谁中小团队技术负责人、独立开发者、正在从单体架构向微服务过渡的项目组。不适合谁已经上云对象存储如 COS/OSS且有成熟 SDK 的大型项目——那属于另一套基建体系。本方案的价值在于“轻量可控”没有额外中间件依赖不引入 Redis 缓存上传状态不改造小程序基础库所有逻辑都在 Maven 工程内闭环。我去年帮一家社区团购小程序落地这套方案从开发环境切到阿里云 ECS Nginx 文件服务器只花了 2 小时改配置、15 分钟验证连测试同学都没感知到后端变了。2. 整体架构与设计思路拆解2.1 为什么放弃“统一抽象层”选择“配置驱动路由”刚拿到需求时我也想过建个StorageStrategy接口搞LocalStorageImpl和NginxStorageImpl两个实现类再用 Spring 的ConditionalOnProperty注解动态加载。但实操两周后推翻了——太重。问题出在Nginx 文件服务器的本质不是“存储服务”而是“静态资源网关”。它不处理业务逻辑不校验 token不记录元数据只干一件事接收 multipart/form-data 请求原样保存为文件并返回 200。所以NginxStorageImpl实际要做的不是调用某个 SDK而是构造一个符合 Nginx 接收规范的 HTTP 请求发给http://file-server/upload。这和LocalStorageImpl直接file.transferTo()的操作粒度完全不同。强行抽象会导致- 接口方法签名膨胀upload(MultipartFile file, String bucket, String prefix)中bucket对本地无意义- 异常处理割裂本地 IO 异常 vs 网络超时异常- 日志埋点混乱本地存成功打“写入磁盘”Nginx 存成功打“转发完成”语义不一致。最终采用“配置驱动路由”是回归本质让每个存储路径各司其职用最直白的方式做最该做的事。UploadService不是策略容器而是路由中枢。它的核心逻辑只有三行if (local.equals(uploadStrategy)) { return localUploader.upload(file); // 返回 FileEntity含本地路径 } else if (nginx.equals(uploadStrategy)) { return nginxUploader.upload(file); // 返回 FileEntity含 Nginx URL }而localUploader和nginxUploader是两个彻底解耦的 Bean互不引用甚至包路径都不同com.example.upload.localvscom.example.upload.nginx。这样做的好处是-可测试性极强单元测试时Mock 其中一个 Bean另一个完全隔离-可替换性极高明天要切到 MinIO只需新增MinioUploader类改配置即可不影响现有逻辑-可观察性极佳监控大盘上“本地上传成功率”和“Nginx 上传成功率”天然分开展示故障定位秒级。提示不要在UploadService里写业务判断逻辑如“图片小于 1MB 走本地否则走 Nginx”。这种规则应前置到网关层或小程序端后端只做确定性路由。我们曾因在 Service 里加了尺寸判断导致灰度期间部分用户上传失败——因为前端未同步更新尺寸校验逻辑后端收到超大文件却按本地路径处理磁盘爆满。教训是路由决策必须绝对确定、绝对可预测。2.2 前端如何做到“上传路径透明”小程序端的封装是这套方案能落地的关键。很多团队前端自己拼接 URL比如// ❌ 错误示范硬编码路径 const url env dev ? /api/upload/local : /api/upload/nginx; wx.uploadFile({ url, filePath, name: file });这等于把后端配置泄露到前端违背了“配置驱动”原则。本项目采用“统一上传入口 后端下发策略”方案小程序启动时调用/api/upload/config接口GET获取当前环境的上传策略json { strategy: nginx, baseUrl: https://file.example.com }将结果存入wx.setStorageSync(uploadConfig, config)全局可用所有页面调用统一工具方法javascript // utils/upload.js export function uploadFile(filePath) { const config wx.getStorageSync(uploadConfig); return new Promise((resolve, reject) { wx.uploadFile({ url: ${config.baseUrl}/upload, // 动态拼接 filePath, name: file, success: (res) { const data JSON.parse(res.data); if (data.errno 0) { resolve(data.data.url); // 统一返回可访问 URL } else { reject(data); } } }); }); }这个设计看似多了一次请求实则带来三大收益-前端零配置project.config.json里不需要写任何环境变量app.js的onLaunch里自动拉取-热更新能力运维半夜切存储策略小程序不用发版下次启动自动生效-降级友好如果/api/upload/config接口挂了前端 fallback 到内置默认策略如local保证基础功能可用。注意/api/upload/config接口本身必须高可用。我们在 Nginx 层做了缓存add_header Cache-Control public, max-age3600并设置 5 秒超时。实测发现99% 的小程序启动时这个请求耗时 200ms比加载一张 10KB 图片还快。2.3 Nginx 文件服务器为何不直接暴露给小程序你可能会问既然 Nginx 能直接接收上传为什么小程序不直连https://file.example.com/upload还要绕一层 Java 后端答案是安全边界与业务合规。Token 校验小程序上传必须携带有效的登录态如Authorization: Bearer xxxNginx 无法解析 JWT 或校验 session只能由 Java 后端完成鉴权后再以可信身份转发给 Nginx文件名净化用户上传的原始文件名可能含../、script等恶意字符串Nginx 不做处理直接保存会引发路径遍历或 XSSJava 层必须重命名如UUID 时间戳 安全后缀元数据记录上传成功后业务系统通常要记录“谁在什么时候上传了什么文件”这需要写数据库Nginx 做不到错误映射Nginx 返回 502网关错误或 413请求体过大对小程序不友好。Java 层统一捕获转成{errno: 5001, msg: 文件服务器繁忙请稍后重试}。所以Nginx 在这里不是替代后端而是卸载文件 I/O 压力的专用组件。它的配置极简只做三件事1. 接收POST /upload请求2. 将 multipart 数据原样保存到指定目录如/var/www/file/upload/3. 返回200 OK和 JSON 响应体由upload_pass指令控制。真正的“智能”全在 Java 层Nginx 只是高效、可靠的“搬运工”。3. 核心细节解析与实操要点3.1 后端双路径实现的关键差异点本地存储IndexController这是最简单的路径但细节决定成败。IndexController的核心方法PostMapping(/upload/local) public ResponseEntityUploadResult uploadLocal(RequestParam(file) MultipartFile file) { try { String originalName file.getOriginalFilename(); String safeName FilenameUtils.getName(originalName); // 防止 ../ String ext FilenameUtils.getExtension(safeName).toLowerCase(); String fileName UUID.randomUUID().toString() _ System.currentTimeMillis() . ext; Path uploadPath Paths.get(uploadDir, fileName); // uploadDir 来自配置 Files.createDirectories(uploadPath.getParent()); // 自动创建目录 file.transferTo(uploadPath.toFile()); String relativeUrl /file/ fileName; // 前端访问路径 return ResponseEntity.ok(UploadResult.success(relativeUrl)); } catch (IOException e) { log.error(本地上传失败, e); return ResponseEntity.status(500).body(UploadResult.fail(上传失败)); } }关键细节-FilenameUtils.getName()是 Apache Commons IO 的方法必须用它而非originalName.substring(originalName.lastIndexOf(/)1)因为 Windows 用户可能传C:\fakepath\abc.jpg后者会截出C:\fakepath\abc.jpg导致路径遍历-Files.createDirectories()必须显式调用否则当uploadDir不存在时transferTo()抛NoSuchFileException而不是自动创建-relativeUrl以/file/开头是为了和 Nginx 路径对齐Nginx 配置location /file/ { alias /var/www/file/; }前端无需区分路径格式。Nginx 存储NginxControllerNginxController的难点不在 Java 代码而在如何让 Nginx 正确接收并响应 multipart 请求。标准 Nginx 配置默认不支持multipart/form-data的完整解析必须配合upload_pass指令需编译nginx-upload-module或改用proxy_pass。本项目采用后者更通用# nginx.conf upstream file_server { server 192.168.1.100:8080; # Nginx 文件服务器地址 } server { listen 80; server_name file.example.com; location /upload { proxy_pass http://file_server/upload; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Content-Type $content_type; # 关键透传 Content-Type client_max_body_size 100m; # 必须大于小程序最大上传限制 proxy_read_timeout 300; # 大文件上传超时 } location /file/ { alias /var/www/file/; expires 1h; } }对应的 Java 代码PostMapping(/upload/nginx) public ResponseEntityUploadResult uploadNginx(RequestParam(file) MultipartFile file) { try { // 1. 重命名同本地逻辑 String ext FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase(); String fileName UUID.randomUUID().toString() _ System.currentTimeMillis() . ext; // 2. 构造转发请求 String nginxUrl nginxUploadUrl ?filename fileName; // 透传文件名 HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMapString, Object body new LinkedMultiValueMap(); body.add(file, new ByteArrayResource(file.getBytes()) { Override public String getFilename() { return fileName; // 强制覆盖原始文件名 } }); HttpEntityMultiValueMapString, Object requestEntity new HttpEntity(body, headers); // 3. 发送请求使用 RestTemplate ResponseEntityString response restTemplate.postForEntity( nginxUrl, requestEntity, String.class); // 4. 解析 Nginx 返回的 JSON JSONObject json JSONObject.parseObject(response.getBody()); String nginxUrlPath json.getString(url); // Nginx 返回 {url: /file/xxx.jpg} return ResponseEntity.ok(UploadResult.success(nginxUrlPath)); } catch (Exception e) { log.error(Nginx 上传失败, e); return ResponseEntity.status(500).body(UploadResult.fail(上传失败)); } }关键细节-nginxUploadUrl必须是http://file.example.com/upload不能是http://192.168.1.100:8080/upload否则跨域且不安全-?filename参数是让 Nginx 服务端知道该存成什么名字避免后端再次解析 multipart-ByteArrayResource的getFilename()方法必须重写否则 Nginx 收到的文件名仍是原始名含恶意字符-RestTemplate必须配置setConnectTimeout(5000)和setReadTimeout(300000)否则大文件上传时线程卡死。内聚型 UploadController调试专用这个 Controller 的存在纯粹为了开发效率。它把所有逻辑塞进一个方法方便断点调试PostMapping(/upload/debug) public MapString, Object uploadDebug( RequestParam(file) MultipartFile file, RequestParam(value strategy, defaultValue local) String strategy) { // 直接根据参数切换逻辑不读配置 if (local.equals(strategy)) { // 复制 IndexController 逻辑 } else { // 复制 NginxController 逻辑 } }使用场景- 前端同事说“上传失败”你不用改配置、重启服务直接在小程序里把url改成/api/upload/debug?strategynginx秒级复现- 测试 Nginx 服务是否正常curl 命令直连bash curl -F filetest.jpg http://localhost:8080/api/upload/debug?strategynginx- 新增一种存储方式如 FTP先在这个 Controller 里验证通路再拆分成标准 MVC 结构。注意UploadController必须加Profile(!prod)注解确保生产环境打包时自动排除避免安全风险。3.2 小程序端上传封装的深度优化utils/upload.js不只是简单封装wx.uploadFile它解决了三个高频痛点进度监听的可靠性小程序wx.uploadFile的onProgressUpdate回调在 iOS 上有严重缺陷当网络波动时进度可能卡在 99% 不动或直接跳到 100% 但实际未完成。本项目采用“双校验机制”- 前端监听进度但不作为完成依据- 上传成功后立即发起一次HEAD请求校验文件是否存在javascript success: (res) { const data JSON.parse(res.data); if (data.errno 0) { // 发起 HEAD 校验 wx.headFile({ url: data.data.url, success: () resolve(data.data.url), fail: () reject({ errno: 5002, msg: 文件校验失败 }) }); } }这样即使 Nginx 返回了 200但文件实际没写完如磁盘满HEAD会失败前端可提示重试。错误分类与友好提示小程序上传失败原因五花八门直接抛res.errMsg给用户看是灾难-request:fail timeout→ “网络开小差了点重试”-request:fail net::ERR_CONNECTION_REFUSED→ “服务暂时不可用请稍后再试”-request:fail statusCode 413→ “文件太大啦请压缩到 10MB 以内”-request:fail statusCode 500→ “服务器出小状况工程师正在抢修”。upload.js内置了完整的错误码映射表并支持自定义文案const ERROR_MAP { timeout: { code: 1001, msg: 网络开小差了点重试 }, 413: { code: 1002, msg: 文件太大啦请压缩到 10MB 以内 }, 500: { code: 1003, msg: 服务器出小状况工程师正在抢修 } };断点续传的轻量实现虽然小程序官方不支持断点续传但我们可以用“文件指纹 服务端去重”模拟1. 上传前前端计算文件 MD5使用wx.getFileSystemManager().readFile分块读取2. 调用/api/upload/check?md5xxx接口查询该文件是否已存在3. 如果存在直接返回已有 URL跳过上传。这个功能在upload.js中作为可选开关export function uploadFile(filePath, options {}) { const { enableDedup false } options; if (enableDedup) { const md5 await calculateMD5(filePath); // 分块计算不卡 UI const existUrl await checkFileExist(md5); if (existUrl) return existUrl; } // ... 执行上传 }实测 5MB 文件 MD5 计算耗时 800ms用户无感知。4. 实操过程与核心环节实现4.1 本地环境快速启动指南假设你有一台开发机Windows/Mac/Linux想 5 分钟跑通本地上传步骤 1准备 Java 环境JDK 8推荐 OpenJDK 11Maven 3.6IDEIntelliJ IDEA 或 VS Code Java Extension Pack。步骤 2导入项目解压资源包找到MyFileUploadDemo目录IntelliJFile → Open → 选择 pom.xmlVS Code打开文件夹点击pom.xml右下角弹出“Import project?”点 Yes。步骤 3修改本地配置编辑src/main/resources/application.ymlserver: port: 8080 upload: strategy: local # 切换为 local local: upload-dir: /tmp/file-upload # Linux/Mac 路径Windows 改为 C:/temp/file-upload nginx: upload-url: http://localhost:8081/upload # 暂不启用 spring: servlet: context-path: /api提示/tmp/file-upload目录需手动创建否则启动报错。Linux/Mac 执行mkdir -p /tmp/file-uploadWindows 在资源管理器新建C:\temp\file-upload。步骤 4启动后端IntelliJ点击Application.java旁的绿色三角形控制台看到Started Application in X.XXX seconds即成功访问http://localhost:8080/api/upload/config返回json { strategy: local, baseUrl: http://localhost:8080/api }步骤 5导入小程序项目微信开发者工具 → 新建项目→ 选择pages目录AppID 填tourist体验版或你的正式 AppID项目目录选资源包中的pages文件夹点击“编译”看到首页“上传按钮”即可。步骤 6上传测试点击首页“选择图片”选一张 JPG点击“上传”控制台应输出log [upload] 开始上传... [upload] 进度: 30% [upload] 进度: 60% [upload] 进度: 100% [upload] 成功: /file/abc123_1712345678.jpg查看/tmp/file-upload/目录确认文件存在。常见问题排查- 报错Error: ENOENT: no such file or directory, open /tmp/file-upload/...检查upload-dir路径是否存在权限是否可写- 小程序提示“request:fail”但后端日志无记录检查微信开发者工具右上角“详情 → 本地服务 → 不校验合法域名”是否勾选- 上传后返回null检查UploadResultBuilder是否正确序列化Lombok 的Data注解是否生效。4.2 Nginx 文件服务器部署全流程生产环境部署 Nginx 文件服务器需兼顾安全、性能、可观测性。以下是经过线上验证的最小可行配置环境准备一台独立服务器推荐 2C4GUbuntu 22.04 LTSNginx 1.22需支持client_max_body_size和proxy_buffering创建专用用户禁止 root 登录。步骤 1安装与基础配置# Ubuntu sudo apt update sudo apt install nginx -y # 创建文件目录 sudo mkdir -p /var/www/file/upload sudo chown -R www-data:www-data /var/www/file sudo chmod -R 755 /var/www/file # 关闭默认站点 sudo rm /etc/nginx/sites-enabled/default步骤 2编写上传服务配置创建/etc/nginx/conf.d/file-server.confupstream backend { server 127.0.0.1:8080; # 指向 Java 后端用于健康检查等 } server { listen 80; server_name file.example.com; # 上传接口仅接受 POST location /upload { # 安全限制 limit_except POST { deny all; } client_max_body_size 100m; client_body_timeout 300s; client_header_timeout 300s; # 代理到 Java 后端本例中 Java 后端也部署在此机 proxy_pass http://backend/api/upload/nginx; 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 Content-Type $content_type; proxy_set_header X-Original-Filename $arg_filename; # 透传文件名 # 性能优化 proxy_buffering on; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; } # 文件访问静态资源 location /file/ { alias /var/www/file/; expires 1h; add_header Cache-Control public, immutable; # 安全加固禁止执行脚本 location ~ \.(php|jsp|cgi|pl|sh)$ { deny all; } } # 健康检查 location /health { return 200 OK; add_header Content-Type text/plain; } }步骤 3启动与验证# 测试配置 sudo nginx -t # 重载配置 sudo systemctl reload nginx # 验证上传接口模拟 Java 后端转发 curl -X POST http://localhost/upload?filenametest.jpg \ -H Content-Type: multipart/form-data \ -F file/path/to/test.jpg # 验证文件访问 curl -I http://localhost/file/test.jpg # 应返回 200步骤 4Java 后端切换配置修改application.ymlupload: strategy: nginx nginx: upload-url: http://file.example.com/upload # 必须是域名非 IP重启 Java 服务访问/api/upload/config确认返回strategy: nginx。关键经验-proxy_buffering on必须开启否则大文件上传时 Nginx 会缓存整个请求体到内存OOM-X-Original-Filename头用于让 Java 后端知道原始文件名用于日志记录但实际保存名仍由 Java 生成-/health接口供运维监控使用建议接入 Prometheus Grafana。4.3 前后端联调与灰度发布策略真实项目上线不可能一刀切。我们采用“三级灰度”策略第一级配置灰度10% 流量Java 后端增加Value(${upload.strategy.default:local})默认local新增Value(${upload.strategy.gray:nginx})灰度策略在UploadService中按用户 ID 哈希取模java int hash userId.hashCode() 0x7fffffff; String actualStrategy (hash % 100 10) ? grayStrategy : defaultStrategy;这样 10% 的用户走 Nginx其余走本地无需改任何配置。第二级URL 灰度特定页面小程序某些页面如“商家入驻”强制走 Nginx其他页面走本地upload.js支持传参javascript uploadFile(filePath, { forceStrategy: nginx });第三级全量切换一键回滚运维在 Nginx 层加开关nginx map $http_x_upload_strategy $upload_strategy { default local; nginx nginx; }Java 后端读取X-Upload-Strategy头优先级高于配置文件。若 Nginx 服务异常运维将$upload_strategy全部设为local5 秒内生效业务无感。5. 常见问题与排查技巧实录5.1 小程序上传失败的 7 类典型场景及根因现象后端日志特征根因分析解决方案iOS 进度卡在 99%无日志或只有upload startiOS 系统wx.uploadFile在弱网下回调丢失启用HEAD校验前端加超时重试最多 3 次上传后文件内容损坏图片打不开日志显示transferTo success但文件大小为 0MultipartFile.getBytes()在大文件时 OOM应改用transferTo()删除getBytes()相关代码全部走transferTo()Nginx 返回 413 Request Entity Too LargeJava 日志无记录Nginx error.log 有client intended to send too large bodyNginxclient_max_body_size小于小程序maxFileSize将 Nginx 的client_max_body_size设为100mJava 的spring.servlet.context-path保持默认上传成功但 URL 访问 404Java 日志显示success: /file/xxx.jpg但浏览器打不开Nginxalias路径末尾缺少/或location /file/的/匹配不精确alias /var/www/file/;必须带末尾/location /file/必须带末尾/同一文件多次上传生成多个副本数据库记录多条/var/www/file/下多个文件前端未做防重复提交或UploadController未加幂等校验前端按钮上传后置灰后端用Redis SETNX校验 MD510 分钟过期上传时中文文件名乱码日志显示originalFilename: ????.jpgTomcat 默认URIEncoding为 ISO-8859-1在application.yml加server.tomcat.uri-encoding: UTF-8Nginx 上传返回 502 Bad GatewayNginx error.log 有connect() failed (111: Connection refused)Java 后端未启动或nginxUploadUrl配置错误检查nginxUploadUrl是否可达curl -v http://file.example.com/upload确认 Java 服务运行中5.2 Java 后端性能瓶颈排查清单当上传并发升高 50 QPS可能出现以下症状按此清单逐项检查CPU 持续 90%-jstack -l pid查看线程栈重点关注http-nio-8080-exec-*线程是否卡在FileOutputStream.write- 根因本地存储transferTo()是阻塞 IO高并发时线程池耗尽- 方案改用异步线程池Async处理本地上传或直接切 Nginx。内存持续增长GC 频繁-jstat -gc pid观察S0C/S1C和EC使用率- 根因MultipartFile.getBytes()将整个文件读入内存- 方案删除所有getBytes()调用强制走transferTo()。上传耗时 10s-tcpdump -i any port 8080 -w upload.pcap抓包分析- 若抓包显示 TCP 重传则是网络问题若无重传但耗时长则是磁盘 IO 瓶颈- 方案本地存储换 SSDNginx 服务器用 RAID 10。5.3 Nginx 文件服务器安全加固要点生产环境必须执行以下加固措施禁用目录浏览在location /file/块中添加autoindex off;限制文件类型nginx location ~ ^/file/.*\.(php|html|js|css|exe|bat|sh)$ { deny all; }防止盗链nginx location /file/ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } }速率限制nginxlimit_req_zone $binary_remote_addr zoneupload:10m rate5r/s;location /upload {limit_req zoneupload burst10 nodelay;}每 IP 每秒最多 5 次上传突发允许 10 次最后分享一个小技巧在UploadController的uploadDebug方法里加一段日志打印 Nginx 的真实请求头java log.info(Nginx received headers: {}, request.getHeaderNames());这样当出现413或400时你能一眼看到 Nginx 是否收到了Content-Type和Content-Length省去一半排查时间。我在客户现场处理过一次诡异的400 Bad Request最终发现是前端wx.uploadFile的name参数写成了fileName少了个eNginx 因找不到file字段直接拒收——这个日志让我 2 分钟定位否则至少 2 小时。本文还有配套的精品资源点击获取简介微信小程序端支持两种文件上传路径切换无需改代码只需配置。一种走Java后端直接保存到本机磁盘由IndexController接收并落盘另一种对接独立Nginx文件服务器提供两个可选后端入口NginxController走标准MVC分层Controller→Service→Entity适合生产部署UploadController把全部逻辑压在控制器里方便开发阶段快速验证和调试。前端小程序已封装好上传调用、进度监听、错误提示等基础能力含完整项目结构——app.js、app.、project.config.、sitemap.、pages/index页面、utils工具类以及适配的wxss样式。后端基于Maven构建pom.xml已预置Spring Boot、Web、Lombok等依赖src/main下目录结构清晰可直接导入IDE运行。适用于中小项目在开发期用本地存储、上线后平滑切换至专用文件服务器的场景上传路径通过配置项控制不耦合业务逻辑。本文还有配套的精品资源点击获取