{"id":16530534,"url":"https://github.com/freeshineit/gin_router_web","last_synced_at":"2026-03-05T02:04:15.184Z","repository":{"id":48278805,"uuid":"154618173","full_name":"freeshineit/gin_router_web","owner":"freeshineit","description":"gin_router_web 是学习gin 使用的项目，主要包括路由定义、 请求参数的绑定、响应的封装和文件上传（包括大文件分片上传）等","archived":false,"fork":false,"pushed_at":"2026-01-18T11:03:38.000Z","size":454,"stargazers_count":21,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-18T17:46:04.369Z","etag":null,"topics":["ajax","chunk","file","gin","go","golang","post","upload"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/freeshineit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-10-25T06:03:52.000Z","updated_at":"2026-01-18T11:03:42.000Z","dependencies_parsed_at":"2023-02-19T14:45:59.375Z","dependency_job_id":"3c0ca9ff-cceb-4224-b940-d712c34bd861","html_url":"https://github.com/freeshineit/gin_router_web","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/freeshineit/gin_router_web","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freeshineit%2Fgin_router_web","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freeshineit%2Fgin_router_web/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freeshineit%2Fgin_router_web/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freeshineit%2Fgin_router_web/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/freeshineit","download_url":"https://codeload.github.com/freeshineit/gin_router_web/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freeshineit%2Fgin_router_web/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30106167,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T01:39:18.192Z","status":"online","status_checked_at":"2026-03-05T02:00:06.710Z","response_time":93,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","chunk","file","gin","go","golang","post","upload"],"created_at":"2024-10-11T18:06:15.980Z","updated_at":"2026-03-05T02:04:15.176Z","avatar_url":"https://github.com/freeshineit.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gin_router_web\n\n![build](https://github.com/freeshineit/gin_rotuer_web/workflows/build/badge.svg)\n\n[gin](https://github.com/gin-gonic/gin)是简单快速的`golang`框架，这个项目主要是介绍`gin`的路由配置及使用，包括各种HTTP请求方法（GET、POST）和数据格式处理（JSON、XML、Form、URL Encoded）。\n\n**主要特性：**\n- 展示 Gin 框架的多种路由配置方式\n- 支持多种数据格式的请求处理（JSON、XML、Form、URL Encoded）\n- 文件上传功能（单文件及分片上传）\n- 前端与后端交互示例\n\n**技术栈：**\n- Go \u003e= 1.23\n- Gin Web Framework v1.9.1\n- 支持 Docker 部署\n\n\n\n## 使用\n\n```bash\n# development\ngo run main.go\n\n# run development with live reload\n# https://github.com/cosmtrek/air Live reload for Go apps\nair\n\n# build\ngo build\n\n# run production\n# export GIN_MODE=release\n./gin-router-web\n\n# make build -\u003e ./bin/app\nmake build\n\n# server 8080\nhttp://localhost:8080/\n\n# file chunk upload\nhttp://localhost:8080/upload_chunks\n\n# docker deploy\nmake deploy\n\n# dependency management\ngo mod tidy\n```\n\n## 项目理念与设计\n\n本项目是一个 **Gin 框架学习和示例项目**，旨在展示：\n\n### 核心特性\n1. **路由配置** - 展示 Gin 框架的路由分组、中间件、静态资源配置等\n2. **多种数据格式处理** - JSON、XML、Form、URL Encoded 等多种数据格式的处理方式\n3. **文件处理** - 单文件上传、批量上传、分片上传完整解决方案\n4. **Web 应用** - 包含前后端交互的完整示例\n\n### 架构思想\n- **分层设计** - API层、路由层、模型层清晰分离\n- **可扩展性** - 新增 API 只需在 `api/` 目录添加处理函数，在 `route.go` 注册路由\n- **代码复用** - 响应格式统一通过 `helper` 模块处理\n- **前后端分离** - 前端页面独立，便于前后端开发解耦\n\n## 项目结构\n\n```\ngin_router_web/\n├── api/                   # API 处理层\n│   ├── handle_func.go    # 各类型数据处理函数\n│   ├── upload.go         # 文件上传相关\n│   └── web.go            # 网页路由处理\n├── router/\n│   └── route.go          # 路由配置\n├── models/               # 数据模型\n│   ├── chunk_file.go     # 分片文件模型\n│   └── user.go           # 用户模型\n├── helper/\n│   └── respose.go        # 响应格式化工具\n├── templates/            # HTML 模板\n│   ├── index.html        # 首页\n│   └── upload_chunks.html # 分片上传页面\n├── public/               # 静态资源\n│   └── static/\n│       ├── css/\n│       │   └── common.css\n│       └── js/\n│           ├── index.js\n│           └── plupload.full.min.js\n├── main.go              # 应用入口\n├── go.mod               # Go 模块定义\n├── Dockerfile           # Docker 配置\n├── docker-compose.yml   # Docker Compose 配置\n├── Makefile            # 构建脚本\n└── README.md           # 项目文档\n```\n\n### 关键模块说明\n\n| 模块 | 描述 |\n|-----|------|\n| **api** | 包含所有的 HTTP 请求处理函数，支持多种数据格式 |\n| **router** | 路由配置层，定义所有的 API 端点和Web路由 |\n| **models** | 数据模型定义，如用户信息、分片文件等 |\n| **helper** | 工具函数，如统一的响应格式化 |\n| **templates** | HTML 模板文件，提供前端界面 |\n| **public** | 静态资源（CSS、JavaScript等） |\n\n## API 接口列表\n\n```md\n[GET]    /                       首页\n[GET]    /upload_chunks          分片上传页面\n[GET]    /api/query              查询参数示例\n\n[POST]   /api/form_post          表单数据提交\n[POST]   /api/json_post          JSON 数据提交\n[POST]   /api/urlencoded_post    URL 编码数据提交\n[POST]   /api/json_and_form_post JSON 或 Form 数据提交（自动识别）\n[POST]   /api/xml_post           XML 数据提交\n[POST]   /api/file_upload        文件上传\n[POST]   /api/file_chunk_upload  分片文件上传\n```\n\n## 快速开始\n\n### 环境要求\n- Go \u003e= 1.23\n- (可选) Docker \u0026 Docker Compose 用于容器化部署\n\n### 本地运行\n\n```bash\n# 1. 克隆项目\ngit clone https://github.com/freeshineit/gin_router_web.git\ncd gin_router_web\n\n# 2. 安装依赖\ngo mod tidy\n\n# 3. 开发模式运行（需要安装 air）\ngo install github.com/cosmtrek/air@latest\nair\n\n# 或直接运行\ngo run main.go\n\n# 4. 浏览器访问\nhttp://localhost:8080/\n```\n\n### Docker 部署\n\n```bash\n# 构建并运行 Docker 容器\nmake deploy\n\n# 或手动构建\ndocker build -t gin-router-web .\ndocker run -p 8080:8080 gin-router-web\n```\n\n## 静态资源配置\n\n```go\nfunc setStaticFS(r *gin.Engine) {\n\t// 加载 HTML 模板文件\n\tr.LoadHTMLGlob(\"./templates/*.html\")\n\n\t// 配置 favicon\n\tr.StaticFile(\"favicon.ico\", \"./public/favicon.ico\")\n\t\n\t// 配置静态文件目录（CSS、JS 等）\n\tr.StaticFS(\"/static\", http.Dir(\"public/static\"))\n\t\n\t// 配置上传文件目录\n\tr.StaticFS(\"/upload\", http.Dir(\"upload\"))\n}\n```\n\n**关键方法说明：**\n\n- `LoadHTMLGlob(pattern string)` - 加载全局模式的 HTML 文件标识，并与 HTML 渲染器关联\n- `StaticFile(relativePath, filepath string)` - 配置单个静态文件\n- `StaticFS(relativePath string, fs http.FileSystem)` - 配置静态资源目录\n\n## 路由配置详解\n\n### Web 路由\n\n```go\nfunc setWebRoute(r *gin.Engine) {\n\t// 首页路由\n\tr.GET(\"/\", api.WebIndex)\n\t\n\t// 分片上传页面\n\tr.GET(\"/upload_chunks\", api.WebUploadChunks)\n}\n```\n\n### API 路由分组\n\n```go\nfunc SetupRoutes() *gin.Engine {\n\tr := gin.Default()\n\n\t// 设置静态资源\n\tsetStaticFS(r)\n\n\t// 设置Web路由\n\tsetWebRoute(r)\n\n\t// API 路由分组\n\tapiGroup := r.Group(\"/api\")\n\t{\n\t\t// 表单提交\n\t\tapiGroup.POST(\"/form_post\", api.FormPost)\n\n\t\t// JSON 提交\n\t\tapiGroup.POST(\"/json_post\", api.JSONPost)\n\n\t\t// URL 编码提交\n\t\tapiGroup.POST(\"/urlencoded_post\", api.UrlencodedPost)\n\n\t\t// JSON 和 Form 混合提交（自动识别）\n\t\tapiGroup.POST(\"/json_and_form_post\", api.JSONAndFormPost)\n\n\t\t// XML 提交\n\t\tapiGroup.POST(\"/xml_post\", api.XMLPost)\n\n\t\t// 文件上传\n\t\tapiGroup.POST(\"/file_upload\", api.FileUpload)\n\n\t\t// 文件分片上传\n\t\tapiGroup.POST(\"/file_chunk_upload\", api.FileChunkUpload)\n\n\t\t// 查询参数示例\n\t\tapiGroup.GET(\"/query\", func(c *gin.Context) {\n\t\t\tname := c.Query(\"name\")\n\t\t\tmessage := c.Query(\"message\")\n\t\t\tnick := c.DefaultQuery(\"nick\", \"anonymous\")\n\n\t\t\tc.JSON(http.StatusOK, helper.BuildResponse(gin.H{\n\t\t\t\t\"name\":    name,\n\t\t\t\t\"message\": message,\n\t\t\t\t\"nick\":    nick,\n\t\t\t}))\n\t\t})\n\t}\n\n\treturn r\n}\n```\n\n**关键概念：**\n- **路由分组** (`r.Group()`) - 对多个相关路由进行分组，便于批量配置中间件或路径前缀\n- **Query 参数** - 通过 URL 查询字符串传递，如 `?name=value\u0026message=test`\n- **DefaultQuery** - 获取查询参数，提供默认值\n\n## HTTP 请求数据格式\n\n在 HTTP 通信中，请求体的数据格式由 `Content-Type` 头部指定。本项目展示如何处理常见的数据格式：\n\n| Content-Type | 用途 | 例子 |\n|-------------|------|-----|\n| `text/plain` | 纯文本 | 简单文本消息 |\n| `text/html` | HTML 文档 | 网页内容 |\n| `application/json` | JSON 格式数据 | REST API 的标准格式 |\n| `application/x-www-form-urlencoded` | 表单数据 | HTML 表单默认格式 |\n| `application/xml` | XML 格式数据 | 配置文件、SOAP 通信 |\n| `multipart/form-data` | 多部分数据 | 文件上传、混合数据 |\n\n参考：[MIME 类型](https://zh.wikipedia.org/wiki/MIME)\n\n### 1. Form 表单提交 (`application/x-www-form-urlencoded`)\n\n**数据模型**\n\n```go\n// User 用户信息结构体，支持 JSON、Form 和 XML 三种格式\ntype User struct {\n\tName    string `json:\"name\" form:\"name\" xml:\"name\"`\n\tMessage string `json:\"message\" form:\"message\" xml:\"message\"`\n\tNick    string `json:\"nick\" form:\"nick\" xml:\"nick\"`\n}\n```\n\n**后端实现** (api/handle_func.go)\n\n```go\n// FormPost 处理表单数据提交\nfunc FormPost(c *gin.Context) {\n\t// 方式1：逐个读取字段\n\tmessage := c.PostForm(\"message\")\n\tnick := c.DefaultPostForm(\"nick\", \"default nick\")\n\tname := c.DefaultPostForm(\"name\", \"default name\")\n\t\n\tuser := User{\n\t\tName:    name,\n\t\tNick:    nick,\n\t\tMessage: message,\n\t}\n\n\t// 方式2：自动绑定（推荐）\n\t// user := \u0026User{}\n\t// c.ShouldBind(user)\n\n\tc.JSON(http.StatusOK, helper.BuildResponse(user))\n}\n```\n\n**前端实现** (templates/index.html)\n\n```html\n\u003cform method=\"post\" action=\"/api/form_post\" id=\"form\"\u003e\n  \u003cdiv class=\"form-item\"\u003e\n    \u003clabel for=\"name\"\u003e名字\u003c/label\u003e\n    \u003cinput type=\"text\" id=\"name\" name=\"name\" /\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"form-item\"\u003e\n    \u003clabel for=\"message\"\u003e消息\u003c/label\u003e\n    \u003cinput type=\"text\" id=\"message\" name=\"message\" /\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"form-item\"\u003e\n    \u003clabel for=\"nick\"\u003e昵称\u003c/label\u003e\n    \u003cinput type=\"text\" id=\"nick\" name=\"nick\" /\u003e\n  \u003c/div\u003e\n  \u003cbutton type=\"submit\"\u003e提交\u003c/button\u003e\n\u003c/form\u003e\n```\n\n**关键知识点：**\n- `c.PostForm(key)` - 读取表单字段\n- `c.DefaultPostForm(key, defaultValue)` - 读取表单字段，提供默认值\n- `c.ShouldBind()` - 自动将表单数据绑定到结构体（推荐）\n\n### 2. JSON 数据提交 (`application/json`)\n\n**后端实现**\n\n```go\n// JSONPost 处理 JSON 格式数据\nfunc JSONPost(c *gin.Context) {\n\tvar user User\n\t\n\t// 绑定 JSON 数据到结构体\n\tif err := c.BindJSON(\u0026user); err != nil {\n\t\tc.AbortWithStatusJSON(http.StatusOK, helper.BuildErrorResponse(\n\t\t\thttp.StatusBadRequest, \n\t\t\t\"invalid parameter\",\n\t\t))\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, helper.BuildResponse(user))\n}\n```\n\n**前端实现** (JavaScript/Axios)\n\n```js\nconst data = {\n  name: \"shine\",\n  message: \"hello gin\",\n  nick: \"shineshao\"\n};\n\naxios({\n  method: \"post\",\n  url: \"/api/json_post\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n  },\n  data, // 自动序列化为 JSON\n}).then((res) =\u003e {\n  console.log(res.data);\n  $(\".json-msg\").text(`success at ${new Date()}`);\n});\n```\n\n**关键知识点：**\n- `c.BindJSON()` - 解析 JSON 请求体并绑定到结构体\n- Axios 会自动设置 `Content-Type: application/json`\n\n### 3. URL 编码数据提交 (`application/x-www-form-urlencoded`)\n\n**后端实现**\n\n```go\n// UrlencodedPost 处理 URL 编码格式数据\nfunc UrlencodedPost(c *gin.Context) {\n\t// 从查询参数读取 limit\n\tlimit := c.Query(\"limit\")\n\t\n\t// 从 POST 表单数据读取\n\tname := c.PostForm(\"name\")\n\tmessage := c.PostForm(\"message\")\n\tnick := c.DefaultPostForm(\"nick\", \"default\")\n\t\n\tuser := User{\n\t\tName:    name,\n\t\tNick:    nick,\n\t\tMessage: message,\n\t}\n\n\tlog.Printf(\"request query limit: %s\\n\", limit)\n\tc.JSON(http.StatusOK, helper.BuildResponse(user))\n}\n```\n\n**前端实现**\n\n```js\nconst data = {\n  name: \"shine\",\n  message: \"hello gin\",\n  nick: \"shineshao\"\n};\n\n// 注意：查询参数在 URL 中，表单数据在请求体中\naxios({\n  method: \"post\",\n  url: \"/api/urlencoded_post?limit=100\",\n  headers: {\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n  },\n  data: $.param(data), // 使用 $.param() 进行 URL 编码\n}).then((res) =\u003e {\n  console.log(res.data);\n  $(\".urlencoded-msg\").text(`success at ${new Date()}`);\n});\n```\n\n**关键知识点：**\n- `c.Query()` - 读取 URL 查询参数\n- `c.PostForm()` - 读取 POST 表单数据\n- 使用 `$.param()` 进行 URL 编码\n\n### 4. JSON 或 Form 混合提交 (自动识别)\n\n**后端实现**\n\n```go\n// JSONAndFormPost 自动识别 JSON 或 Form 格式\n// 可以同时处理 application/json 和 application/x-www-form-urlencoded\nfunc JSONAndFormPost(c *gin.Context) {\n\tvar user User\n\n\t// ShouldBind() 根据 Content-Type 自动选择合适的绑定方式\n\tif err := c.ShouldBind(\u0026user); err != nil {\n\t\tc.AbortWithStatusJSON(http.StatusOK, helper.BuildErrorResponse(\n\t\t\thttp.StatusBadRequest, \n\t\t\t\"invalid parameter\",\n\t\t))\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, helper.BuildResponse(user))\n}\n```\n\n**前端实现 - JSON 方式**\n\n```js\nconst data = {\n  name: \"shine\",\n  message: \"hello gin\",\n  nick: \"shineshao\"\n};\n\naxios({\n  method: \"post\",\n  url: \"/api/json_and_form_post\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n  },\n  data,\n}).then((res) =\u003e {\n  console.log(res.data);\n  $(\".jsonandform-msg\").text(`success with JSON at ${new Date()}`);\n});\n```\n\n**前端实现 - Form 方式**\n\n```js\naxios({\n  method: \"post\",\n  url: \"/api/json_and_form_post\",\n  headers: {\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n  },\n  data: $.param(data),\n}).then((res) =\u003e {\n  console.log(res.data);\n  $(\".jsonandform-msg\").text(`success with Form at ${new Date()}`);\n});\n```\n\n**关键知识点：**\n- `c.ShouldBind()` - 自动识别 Content-Type，使用相应的绑定方式\n- 同一个接口可以处理多种数据格式\n\n### 5. XML 数据提交 (`application/xml`)\n\n**后端实现**\n\n```go\n// XMLPost 处理 XML 格式数据\nfunc XMLPost(c *gin.Context) {\n\tvar user User\n\n\t// 绑定 XML 数据到结构体\n\tif err := c.BindXML(\u0026user); err != nil {\n\t\tc.AbortWithStatusJSON(http.StatusOK, helper.BuildErrorResponse(\n\t\t\thttp.StatusBadRequest, \n\t\t\t\"invalid parameter\",\n\t\t))\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, helper.BuildResponse(user))\n}\n```\n\n**前端实现**\n\n```js\nconst data = {\n  name: \"shine\",\n  message: \"hello gin\",\n  nick: \"shineshao\"\n};\n\n// 手动构建 XML 字符串\nconst xmlData = `\u003cxml\u003e\n  \u003cname\u003e${data.name}\u003c/name\u003e\n  \u003cmessage\u003e${data.message}\u003c/message\u003e\n  \u003cnick\u003e${data.nick}\u003c/nick\u003e\n\u003c/xml\u003e`;\n\naxios({\n  method: \"post\",\n  url: \"/api/xml_post\",\n  headers: {\n    \"Content-Type\": \"application/xml\",\n  },\n  data: xmlData,\n}).then((res) =\u003e {\n  console.log(res.data);\n  $(\".xml-msg\").text(`success at ${new Date()}`);\n});\n```\n\n**关键知识点：**\n- `c.BindXML()` - 解析 XML 请求体并绑定到结构体\n- 需要在结构体标签中指定 `xml:\"fieldname\"` 标签\n- XML 通常用于旧系统集成、配置文件等场景\n\n### 6. 文件上传 (`multipart/form-data`)\n\n**数据模型**\n\n```go\n// ChunkFile 分片文件信息\ntype ChunkFile struct {\n\tName   string `json:\"name\" form:\"name\"`       // 文件名\n\tChunk  int    `json:\"chunk\" form:\"chunk\"`     // 当前分片号\n\tChunks int    `json:\"chunks\" form:\"chunks\"`   // 总分片数\n}\n```\n\n**后端实现** (api/upload.go)\n\n```go\n// FileUpload 处理文件上传\nfunc FileUpload(c *gin.Context) {\n\tfilesUrl := make([]string, 0)\n\t\n\t// 获取多部分表单数据\n\tform, err := c.MultipartForm()\n\tif err != nil {\n\t\tlog.Println(\"MultipartForm error: %s\", err)\n\t\treturn\n\t}\n\n\t// 获取所有 \"file\" 字段的文件\n\tfiles := form.File[\"file\"]\n\n\t// 创建 upload 目录（如果不存在）\n\tif _, err := os.Stat(\"upload\"); err != nil {\n\t\tos.Mkdir(\"upload\", os.ModePerm)\n\t}\n\n\t// 保存所有上传的文件\n\tfor _, file := range files {\n\t\tlog.Println(\"Uploading file:\", file.Filename)\n\n\t\t// 保存文件到指定位置\n\t\tif err := c.SaveUploadedFile(file, \"upload/\"+file.Filename); err != nil {\n\t\t\tlog.Println(\"SaveUploadedFile error: %s\", err)\n\t\t\treturn\n\t\t}\n\t\tfilesUrl = append(filesUrl, \"upload/\"+file.Filename)\n\t}\n\n\tc.JSON(http.StatusOK, helper.BuildResponse(gin.H{\n\t\t\"urls\": filesUrl,\n\t}))\n}\n```\n\n**前端实现 - HTML**\n\n```html\n\u003cdiv\u003e\n  \u003cform id=\"multipleForm\"\u003e\n    \u003c!-- multiple 属性支持多文件选择 --\u003e\n    \u003cinput\n      type=\"file\"\n      name=\"file\"\n      id=\"file\"\n      multiple=\"multiple\"\n      accept=\"image/*\"\n    /\u003e\n  \u003c/form\u003e\n  \u003cbutton class=\"file_upload\"\u003e开始上传文件\u003c/button\u003e\n\u003c/div\u003e\n```\n\n**前端实现 - JavaScript**\n\n```js\n// 单个文件上传\n// const fd = new FormData()\n// const file = document.getElementById('file')\n// fd.append('file', file.files[0])\n\n// 多个文件上传\naxios({\n  method: \"post\",\n  url: \"/api/file_upload\",\n  headers: {\n    \"Content-Type\": \"multipart/form-data\",\n  },\n  data: new FormData($(\"#multipleForm\")[0]),\n}).then((res) =\u003e {\n  console.log(res.data);\n  const urls = res.data.data.urls || [];\n\n  let imgHtml = \"\";\n  for (let i = 0; i \u003c urls.length; i++) {\n    imgHtml += `\u003cdiv\u003e\n      \u003cimg style=\"width: 200px\" src=\"/${urls[i]}\" /\u003e \n      \u003cdiv\u003e${urls[i]}\u003c/div\u003e\n    \u003c/div\u003e`;\n  }\n\n  $(\".file_upload-msg\").html(`\u003cdiv\u003e${new Date()}\u003c/div\u003e${imgHtml}`);\n});\n```\n\n**关键知识点：**\n- `c.MultipartForm()` - 获取多部分表单数据\n- `c.SaveUploadedFile()` - 保存上传的文件\n- `FormData` - JavaScript 用于构建表单数据（包括文件）\n- 使用 `multiple` 属性支持多文件选择\n\n**参考资源：** [Gin 官方文件上传示例](https://github.com/gin-gonic/examples/tree/master/upload-file)\n\n## 文件分片上传\n\n### 工作原理\n\n文件分片上传是一种大文件上传优化技术，通过将大文件拆分成多个小片段，分别上传到服务器：\n\n1. **客户端** - 根据文件大小和设定的分片大小，计算需要的分片数量\n2. **分片上传** - 逐片发送分片数据到服务器\n3. **服务端** - 接收并缓存各片段，当所有片段都收到后，完成文件上传\n\n**优势：**\n- 支持断点续传\n- 网络不稳定时可单独重试失败的分片\n- 提高大文件上传的成功率和用户体验\n\n### 服务器实现\n\n**数据模型** (models/chunk_file.go)\n\n```go\ntype ChunkFile struct {\n\tName   string `json:\"name\" form:\"name\"`       // 文件名\n\tChunk  int    `json:\"chunk\" form:\"chunk\"`     // 当前分片编号（0开始）\n\tChunks int    `json:\"chunks\" form:\"chunks\"`   // 总分片数\n}\n```\n\n**处理函数** (api/upload.go)\n\n```go\n// FileChunkUpload 处理文件分片上传\nfunc FileChunkUpload(c *gin.Context) {\n\tvar chunkFile ChunkFile\n\tr := c.Request\n\n\t// 绑定分片信息\n\tc.Bind(\u0026chunkFile)\n\n\t// 读取上传的文件数据\n\tfile, _, err := r.FormFile(\"file\")\n\tif err != nil {\n\t\tlog.Println(\"FormFile error:\", err)\n\t\treturn\n\t}\n\n\t// 将文件内容读入缓冲区\n\tbuf, _ := ioutil.ReadAll(file)\n\n\t// 文件保存路径\n\tfilePath := \"upload/\" + chunkFile.Name\n\n\t// 打开文件，以追加模式写入（如果不存在则创建）\n\tfd, _ := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)\n\tfd.Write(buf)\n\tfd.Close()\n\n\t// 判断是否是最后一片\n\tif chunkFile.Chunk+1 == chunkFile.Chunks {\n\t\t// 所有分片已上传完成\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"state\": \"SUCCESS\",\n\t\t\t\"url\":   \"/\" + filePath,\n\t\t})\n\t} else {\n\t\t// 还有更多分片待上传\n\t\tc.String(http.StatusOK, \"UPLOADING\")\n\t}\n}\n```\n\n### 客户端实现\n\n项目使用 [plupload](https://www.plupload.com/) 插件实现分片上传。该插件自动处理文件分片、并发上传等细节。\n\n**初始化配置** (templates/upload_chunks.html)\n\n```js\nvar uploader = new plupload.Uploader({\n\truntimes: \"html5,flash,silverlight,html4\",\n\tbrowse_button: \"pickfiles\",\n\tcontainer: document.getElementById(\"container\"),\n\turl: \"/api/file_chunk_upload\",\n\tflash_swf_url: \"/static/js/Moxie.swf\",\n\tsilverlight_xap_url: \"/static/js/Moxie.xap\",\n\t\n\t// 分片大小：100KB\n\tchunk_size: \"100kb\",\n\t\n\t// 文件过滤\n\tfilters: {\n\t\tmax_file_size: \"10mb\",\n\t\tmime_types: [\n\t\t\t{ title: \"Image files\", extensions: \"jpg,gif,png,jpeg\" },\n\t\t\t{ title: \"Zip files\", extensions: \"zip\" },\n\t\t],\n\t},\n\n\tinit: {\n\t\tPostInit: function () {\n\t\t\tdocument.getElementById(\"filelist\").innerHTML = \"\";\n\t\t\tdocument.getElementById(\"uploadfiles\").onclick = function () {\n\t\t\t\tuploader.start();\n\t\t\t\treturn false;\n\t\t\t};\n\t\t},\n\n\t\tFilesAdded: function (up, files) {\n\t\t\t// 文件添加事件\n\t\t\tplupload.each(files, function (file) {\n\t\t\t\tdocument.getElementById(\"filelist\").innerHTML +=\n\t\t\t\t\t'\u003cdiv id=\"' +\n\t\t\t\t\tfile.id +\n\t\t\t\t\t'\"\u003e' +\n\t\t\t\t\tfile.name +\n\t\t\t\t\t\" (\" +\n\t\t\t\t\tplupload.formatSize(file.size) +\n\t\t\t\t\t\") \u003cb\u003e\u003c/b\u003e\u003c/div\u003e\";\n\t\t\t});\n\t\t},\n\n\t\tUploadProgress: function (up, file) {\n\t\t\t// 上传进度事件\n\t\t\tdocument.getElementById(file.id).getElementsByTagName(\"b\")[0].innerHTML =\n\t\t\t\t\"\u003cspan\u003e\" + file.percent + \"%\u003c/span\u003e\";\n\t\t},\n\n\t\tError: function (up, err) {\n\t\t\t// 错误事件\n\t\t\tdocument\n\t\t\t\t.getElementById(\"console\")\n\t\t\t\t.appendChild(\n\t\t\t\t\tdocument.createTextNode(\n\t\t\t\t\t\t\"\\nError #\" + err.code + \": \" + err.message\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t},\n\n\t\tChunkUploaded: function (up, file, info) {\n\t\t\t// 单个分片上传完成事件\n\t\t\tconsole.log(\"Chunk uploaded:\", file.name, info);\n\t\t},\n\t},\n});\n\nuploader.init();\n```\n\n**关键配置参数说明：**\n\n| 参数 | 说明 |\n|-----|------|\n| `url` | 分片上传的服务器接口地址 |\n| `chunk_size` | 分片大小（KB 或 MB） |\n| `max_file_size` | 允许的最大文件大小 |\n| `runtimes` | 支持的运行环境 |\n| `FilesAdded` | 文件选择后的回调 |\n| `UploadProgress` | 上传进度回调 |\n| `ChunkUploaded` | 分片上传完成回调 |\n\n**演示页面：** [http://localhost:8080/upload_chunks](http://localhost:8080/upload_chunks)\n\n**参考资源：**\n- [服务端接口完整代码](https://github.com/freeshineit/gin_rotuer_web/blob/master/api)\n- [客户端文件上传完整代码](https://github.com/freeshineit/gin_rotuer_web/blob/master/templates/upload_chunks.html)\n- [完整项目演示](https://github.com/freeshineit/gin_rotuer_web)\n\n## 最佳实践\n\n### 1. 数据绑定\n\n```go\n// ✅ 推荐：使用 ShouldBind，错误处理更灵活\nvar user User\nif err := c.ShouldBind(\u0026user); err != nil {\n    c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n    return\n}\n\n// ❌ 不推荐：使用 Bind，当绑定失败时会自动响应 400\nc.Bind(\u0026user)\n```\n\n### 2. 响应格式统一\n\n```go\n// 使用 helper 函数统一响应格式\nc.JSON(http.StatusOK, helper.BuildResponse(data))\nc.JSON(http.StatusBadRequest, helper.BuildErrorResponse(400, \"error message\"))\n```\n\n### 3. 文件上传安全性\n\n```go\n// ✅ 验证文件类型\nallowedTypes := map[string]bool{\n    \"image/jpeg\": true,\n    \"image/png\":  true,\n    \"image/gif\":  true,\n}\n\nfileHeader, _ := c.FormFile(\"file\")\nif !allowedTypes[fileHeader.Header.Get(\"Content-Type\")] {\n    c.JSON(http.StatusBadRequest, gin.H{\"error\": \"invalid file type\"})\n    return\n}\n\n// ✅ 限制文件大小\nif fileHeader.Size \u003e 10*1024*1024 { // 10MB\n    c.JSON(http.StatusBadRequest, gin.H{\"error\": \"file too large\"})\n    return\n}\n```\n\n### 4. 错误处理\n\n```go\n// 使用 defer 统一处理错误恢复\ndefer func() {\n    if r := recover(); r != nil {\n        log.Printf(\"Panic recovered: %v\", r)\n        c.JSON(http.StatusInternalServerError, gin.H{\"error\": \"internal server error\"})\n    }\n}()\n```\n\n### 5. 日志记录\n\n```go\n// 记录关键操作\nlog.Printf(\"User: %s, Action: upload, Size: %d bytes, Time: %s\",\n    username,\n    fileHeader.Size,\n    time.Now().Format(\"2006-01-02 15:04:05\"),\n)\n```\n\n## 常见问题\n\n### Q1：上传文件时出现 \"invalid request\" 错误？\n**A：** 检查 `Content-Type` 头是否正确设置为 `multipart/form-data`。使用 Axios 的 FormData 时应该不设置 Content-Type，让浏览器自动处理。\n\n```js\n// ✅ 正确\nconst formData = new FormData();\nformData.append('file', file);\naxios.post('/api/file_upload', formData); // 不设置 headers\n\n// ❌ 错误\naxios.post('/api/file_upload', formData, {\n    headers: { 'Content-Type': 'application/json' }\n});\n```\n\n### Q2：如何处理 CORS 跨域问题？\n**A：** 使用 Gin 的 CORS 中间件：\n\n```bash\ngo get github.com/gin-contrib/cors\n```\n\n```go\nimport \"github.com/gin-contrib/cors\"\n\nr := gin.Default()\nr.Use(cors.Default())\n```\n\n### Q3：大文件上传超时如何解决？\n**A：** 增加服务器的请求超时时间：\n\n```go\nsrv := \u0026http.Server{\n    Addr:         \":8080\",\n    Handler:      r,\n    ReadTimeout:  15 * time.Second,\n    WriteTimeout: 15 * time.Second,\n    IdleTimeout:  60 * time.Second,\n}\n```\n\n### Q4：如何保证文件上传的安全性？\n**A：** 采取以下措施：\n- 验证文件类型（通过 MIME 类型和文件扩展名）\n- 限制文件大小\n- 使用白名单过滤文件名中的特殊字符\n- 存储文件到不可直接访问的目录\n- 使用 HTTPS 传输\n\n```go\n// 文件名清理示例\nfileName := filepath.Base(fileHeader.Filename)\nfileName = strings.Map(func(r rune) rune {\n    if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' || r == '_' || r == '-' {\n        return r\n    }\n    return -1\n}, fileName)\n```\n\n## 学习资源\n\n- **Gin 官方文档**：https://gin-gonic.com/\n- **Go 标准库文档**：https://golang.org/pkg/\n- **HTTP 协议规范**：https://tools.ietf.org/html/rfc7231\n- **Plupload 文档**：https://www.plupload.com/\n\n## 扩展建议\n\n1. **数据库集成** - 添加 GORM 或其他 ORM，持久化用户数据\n2. **身份验证** - 实现 JWT 或 Session 认证机制\n3. **日志系统** - 集成 Logrus 或 Zap 提供结构化日志\n4. **限流控制** - 实现请求限流，防止滥用\n5. **单元测试** - 为 API 编写完整的单元测试\n6. **错误监控** - 集成 Sentry 或其他错误监控服务\n7. **性能优化** - 添加缓存、CDN 支持\n8. **文档自动生成** - 使用 Swag 自动生成 OpenAPI 文档\n\n## 许可证\n\n本项目采用 MIT 许可证。详见 LICENSE 文件。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreeshineit%2Fgin_router_web","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffreeshineit%2Fgin_router_web","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreeshineit%2Fgin_router_web/lists"}