mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
refactor(storage): move file management routes from user to admin namespace
- Remove file list, stats, download, and deletion routes from '/api/v1/upload' - Move these endpoints under '/api/v1/admin/uploads' - Remove user-specific filtering from files query and statistics to aggregate system-wide uploads by default - Allow admins to bypass ownership check when downloading private files - Update backend unit tests, Swagger documentation, and frontend service client and components
This commit is contained in:
+299
-280
@@ -2618,6 +2618,258 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/admin/uploads": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "分页获取系统上传的文件列表,支持文件名关键词、业务类型、扩展名、上传用户ID过滤",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "获取文件列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码(默认 1)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量(默认 20,最大 100)",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件名关键词(模糊匹配)",
|
||||||
|
"name": "keyword",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "业务分类过滤",
|
||||||
|
"name": "type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "扩展名过滤",
|
||||||
|
"name": "extension",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "上传用户 ID",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "查询成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/upload.listFilesResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "未登录",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "无管理员权限",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/uploads/download/batch": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/octet-stream"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "批量打包下载",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "包含文件 ID 数组 of string 的请求体",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/upload.batchDownloadRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功下载打包后的 ZIP",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "参数错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "打包失败",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/uploads/download/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载",
|
||||||
|
"produces": [
|
||||||
|
"application/octet-stream"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "下载单文件",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件 ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "图片质量 (low, medium, high, origin),默认为 origin",
|
||||||
|
"name": "quality",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功下载文件",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "参数错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "文件不存在",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "服务内部错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/uploads/stats": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "返回系统级的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "获取文件统计数据",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "获取成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/upload.fileStatsResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "未登录",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "无管理员权限",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "内部错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/admin/uploads/types": {
|
"/api/v1/admin/uploads/types": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2676,6 +2928,52 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/admin/uploads/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将文件状态置为 deleted(软删除),不会立即清理底层存储对象",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "删除文件",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件 ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "删除成功",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "无权操作",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "文件不存在",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/admin/users": {
|
"/api/v1/admin/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3646,285 +3944,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/upload/download/batch": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/octet-stream"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "批量打包下载",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "包含文件 ID 数组的请求体",
|
|
||||||
"name": "request",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/upload.batchDownloadRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "成功下载打包后的 ZIP",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "参数错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "打包失败",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/download/{id}": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载",
|
|
||||||
"produces": [
|
|
||||||
"application/octet-stream"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "下载单文件",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "文件 ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "图片质量 (low, medium, high, origin),默认为 origin",
|
|
||||||
"name": "quality",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "成功下载文件",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "参数错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "文件不存在",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "服务内部错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/my": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "分页获取当前登录用户上传的文件,支持文件名关键词、业务类型、扩展名过滤",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "获取我的文件列表",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "页码(默认 1)",
|
|
||||||
"name": "page",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "每页数量(默认 20,最大 100)",
|
|
||||||
"name": "page_size",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "文件名关键词(模糊匹配)",
|
|
||||||
"name": "keyword",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "业务分类过滤",
|
|
||||||
"name": "type",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "扩展名过滤",
|
|
||||||
"name": "extension",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "查询成功",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/upload.listMyFilesResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "未登录",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/stats": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "返回当前用户的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "获取文件统计数据",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "获取成功",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/upload.fileStatsResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "未登录",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "内部错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/{id}": {
|
|
||||||
"delete": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "将文件状态置为 deleted(软删除),不会立即清理底层存储对象",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "删除文件",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "文件 ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "删除成功",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "无权操作",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "文件不存在",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/user-info": {
|
"/api/v1/user-info": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -5983,7 +6002,7 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upload.listMyFilesResponse": {
|
"upload.listFilesResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"items": {
|
"items": {
|
||||||
|
|||||||
+299
-280
@@ -2611,6 +2611,258 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/admin/uploads": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "分页获取系统上传的文件列表,支持文件名关键词、业务类型、扩展名、上传用户ID过滤",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "获取文件列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码(默认 1)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量(默认 20,最大 100)",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件名关键词(模糊匹配)",
|
||||||
|
"name": "keyword",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "业务分类过滤",
|
||||||
|
"name": "type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "扩展名过滤",
|
||||||
|
"name": "extension",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "上传用户 ID",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "查询成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/upload.listFilesResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "未登录",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "无管理员权限",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/uploads/download/batch": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/octet-stream"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "批量打包下载",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "包含文件 ID 数组 of string 的请求体",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/upload.batchDownloadRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功下载打包后的 ZIP",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "参数错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "打包失败",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/uploads/download/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载",
|
||||||
|
"produces": [
|
||||||
|
"application/octet-stream"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "下载单文件",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件 ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "图片质量 (low, medium, high, origin),默认为 origin",
|
||||||
|
"name": "quality",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功下载文件",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "参数错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "文件不存在",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "服务内部错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/uploads/stats": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "返回系统级的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "获取文件统计数据",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "获取成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/upload.fileStatsResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "未登录",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "无管理员权限",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "内部错误",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/admin/uploads/types": {
|
"/api/v1/admin/uploads/types": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2669,6 +2921,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/admin/uploads/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将文件状态置为 deleted(软删除),不会立即清理底层存储对象",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "删除文件",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件 ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "删除成功",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "无权操作",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "文件不存在",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.ResponseAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/admin/users": {
|
"/api/v1/admin/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3639,285 +3937,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/upload/download/batch": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/octet-stream"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "批量打包下载",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "包含文件 ID 数组的请求体",
|
|
||||||
"name": "request",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/upload.batchDownloadRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "成功下载打包后的 ZIP",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "参数错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "打包失败",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/download/{id}": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载",
|
|
||||||
"produces": [
|
|
||||||
"application/octet-stream"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "下载单文件",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "文件 ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "图片质量 (low, medium, high, origin),默认为 origin",
|
|
||||||
"name": "quality",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "成功下载文件",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "参数错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "文件不存在",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "服务内部错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/my": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "分页获取当前登录用户上传的文件,支持文件名关键词、业务类型、扩展名过滤",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "获取我的文件列表",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "页码(默认 1)",
|
|
||||||
"name": "page",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "每页数量(默认 20,最大 100)",
|
|
||||||
"name": "page_size",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "文件名关键词(模糊匹配)",
|
|
||||||
"name": "keyword",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "业务分类过滤",
|
|
||||||
"name": "type",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "扩展名过滤",
|
|
||||||
"name": "extension",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "查询成功",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/upload.listMyFilesResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "未登录",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/stats": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "返回当前用户的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "获取文件统计数据",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "获取成功",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/upload.fileStatsResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "未登录",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "内部错误",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/upload/{id}": {
|
|
||||||
"delete": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"SessionCookie": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "将文件状态置为 deleted(软删除),不会立即清理底层存储对象",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"upload"
|
|
||||||
],
|
|
||||||
"summary": "删除文件",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "文件 ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "删除成功",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "无权操作",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "文件不存在",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/util.ResponseAny"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/user-info": {
|
"/api/v1/user-info": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -5976,7 +5995,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upload.listMyFilesResponse": {
|
"upload.listFilesResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"items": {
|
"items": {
|
||||||
|
|||||||
+185
-172
@@ -925,7 +925,7 @@ definitions:
|
|||||||
$ref: '#/definitions/upload.distributionItem'
|
$ref: '#/definitions/upload.distributionItem'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
upload.listMyFilesResponse:
|
upload.listFilesResponse:
|
||||||
properties:
|
properties:
|
||||||
items:
|
items:
|
||||||
items:
|
items:
|
||||||
@@ -2673,6 +2673,190 @@ paths:
|
|||||||
summary: 下载并应用应用更新
|
summary: 下载并应用应用更新
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/api/v1/admin/uploads:
|
||||||
|
get:
|
||||||
|
description: 分页获取系统上传的文件列表,支持文件名关键词、业务类型、扩展名、上传用户ID过滤
|
||||||
|
parameters:
|
||||||
|
- description: 页码(默认 1)
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: 每页数量(默认 20,最大 100)
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
type: integer
|
||||||
|
- description: 文件名关键词(模糊匹配)
|
||||||
|
in: query
|
||||||
|
name: keyword
|
||||||
|
type: string
|
||||||
|
- description: 业务分类过滤
|
||||||
|
in: query
|
||||||
|
name: type
|
||||||
|
type: string
|
||||||
|
- description: 扩展名过滤
|
||||||
|
in: query
|
||||||
|
name: extension
|
||||||
|
type: string
|
||||||
|
- description: 上传用户 ID
|
||||||
|
format: int64
|
||||||
|
in: query
|
||||||
|
name: user_id
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 查询成功
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/util.ResponseAny'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/upload.listFilesResponse'
|
||||||
|
type: object
|
||||||
|
"401":
|
||||||
|
description: 未登录
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"403":
|
||||||
|
description: 无管理员权限
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: 获取文件列表
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
/api/v1/admin/uploads/{id}:
|
||||||
|
delete:
|
||||||
|
description: 将文件状态置为 deleted(软删除),不会立即清理底层存储对象
|
||||||
|
parameters:
|
||||||
|
- description: 文件 ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 删除成功
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"403":
|
||||||
|
description: 无权操作
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"404":
|
||||||
|
description: 文件不存在
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: 删除文件
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
/api/v1/admin/uploads/download/{id}:
|
||||||
|
get:
|
||||||
|
description: 根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载
|
||||||
|
parameters:
|
||||||
|
- description: 文件 ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: 图片质量 (low, medium, high, origin),默认为 origin
|
||||||
|
in: query
|
||||||
|
name: quality
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/octet-stream
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 成功下载文件
|
||||||
|
schema:
|
||||||
|
type: file
|
||||||
|
"400":
|
||||||
|
description: 参数错误
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"404":
|
||||||
|
description: 文件不存在
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"500":
|
||||||
|
description: 服务内部错误
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: 下载单文件
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
/api/v1/admin/uploads/download/batch:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突
|
||||||
|
parameters:
|
||||||
|
- description: 包含文件 ID 数组 of string 的请求体
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/upload.batchDownloadRequest'
|
||||||
|
produces:
|
||||||
|
- application/octet-stream
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 成功下载打包后的 ZIP
|
||||||
|
schema:
|
||||||
|
type: file
|
||||||
|
"400":
|
||||||
|
description: 参数错误
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"500":
|
||||||
|
description: 打包失败
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: 批量打包下载
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
/api/v1/admin/uploads/stats:
|
||||||
|
get:
|
||||||
|
description: 返回系统级的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 获取成功
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/util.ResponseAny'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/upload.fileStatsResponse'
|
||||||
|
type: object
|
||||||
|
"401":
|
||||||
|
description: 未登录
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"403":
|
||||||
|
description: 无管理员权限
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
"500":
|
||||||
|
description: 内部错误
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.ResponseAny'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: 获取文件统计数据
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/api/v1/admin/uploads/types:
|
/api/v1/admin/uploads/types:
|
||||||
get:
|
get:
|
||||||
description: 返回数据库中所有已上传文件实际拥有的业务类型列表
|
description: 返回数据库中所有已上传文件实际拥有的业务类型列表
|
||||||
@@ -3286,177 +3470,6 @@ paths:
|
|||||||
summary: 上传文件
|
summary: 上传文件
|
||||||
tags:
|
tags:
|
||||||
- upload
|
- upload
|
||||||
/api/v1/upload/{id}:
|
|
||||||
delete:
|
|
||||||
description: 将文件状态置为 deleted(软删除),不会立即清理底层存储对象
|
|
||||||
parameters:
|
|
||||||
- description: 文件 ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: 删除成功
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
"403":
|
|
||||||
description: 无权操作
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
"404":
|
|
||||||
description: 文件不存在
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
security:
|
|
||||||
- SessionCookie: []
|
|
||||||
summary: 删除文件
|
|
||||||
tags:
|
|
||||||
- upload
|
|
||||||
/api/v1/upload/download/{id}:
|
|
||||||
get:
|
|
||||||
description: 根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载
|
|
||||||
parameters:
|
|
||||||
- description: 文件 ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: 图片质量 (low, medium, high, origin),默认为 origin
|
|
||||||
in: query
|
|
||||||
name: quality
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/octet-stream
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: 成功下载文件
|
|
||||||
schema:
|
|
||||||
type: file
|
|
||||||
"400":
|
|
||||||
description: 参数错误
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
"404":
|
|
||||||
description: 文件不存在
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
"500":
|
|
||||||
description: 服务内部错误
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
security:
|
|
||||||
- SessionCookie: []
|
|
||||||
summary: 下载单文件
|
|
||||||
tags:
|
|
||||||
- upload
|
|
||||||
/api/v1/upload/download/batch:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: 传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突
|
|
||||||
parameters:
|
|
||||||
- description: 包含文件 ID 数组的请求体
|
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/upload.batchDownloadRequest'
|
|
||||||
produces:
|
|
||||||
- application/octet-stream
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: 成功下载打包后的 ZIP
|
|
||||||
schema:
|
|
||||||
type: file
|
|
||||||
"400":
|
|
||||||
description: 参数错误
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
"500":
|
|
||||||
description: 打包失败
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
security:
|
|
||||||
- SessionCookie: []
|
|
||||||
summary: 批量打包下载
|
|
||||||
tags:
|
|
||||||
- upload
|
|
||||||
/api/v1/upload/my:
|
|
||||||
get:
|
|
||||||
description: 分页获取当前登录用户上传的文件,支持文件名关键词、业务类型、扩展名过滤
|
|
||||||
parameters:
|
|
||||||
- description: 页码(默认 1)
|
|
||||||
in: query
|
|
||||||
name: page
|
|
||||||
type: integer
|
|
||||||
- description: 每页数量(默认 20,最大 100)
|
|
||||||
in: query
|
|
||||||
name: page_size
|
|
||||||
type: integer
|
|
||||||
- description: 文件名关键词(模糊匹配)
|
|
||||||
in: query
|
|
||||||
name: keyword
|
|
||||||
type: string
|
|
||||||
- description: 业务分类过滤
|
|
||||||
in: query
|
|
||||||
name: type
|
|
||||||
type: string
|
|
||||||
- description: 扩展名过滤
|
|
||||||
in: query
|
|
||||||
name: extension
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: 查询成功
|
|
||||||
schema:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/util.ResponseAny'
|
|
||||||
- properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/upload.listMyFilesResponse'
|
|
||||||
type: object
|
|
||||||
"401":
|
|
||||||
description: 未登录
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
security:
|
|
||||||
- SessionCookie: []
|
|
||||||
summary: 获取我的文件列表
|
|
||||||
tags:
|
|
||||||
- upload
|
|
||||||
/api/v1/upload/stats:
|
|
||||||
get:
|
|
||||||
description: 返回当前用户的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: 获取成功
|
|
||||||
schema:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/util.ResponseAny'
|
|
||||||
- properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/upload.fileStatsResponse'
|
|
||||||
type: object
|
|
||||||
"401":
|
|
||||||
description: 未登录
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
"500":
|
|
||||||
description: 内部错误
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/util.ResponseAny'
|
|
||||||
security:
|
|
||||||
- SessionCookie: []
|
|
||||||
summary: 获取文件统计数据
|
|
||||||
tags:
|
|
||||||
- upload
|
|
||||||
/api/v1/user-info:
|
/api/v1/user-info:
|
||||||
get:
|
get:
|
||||||
description: 返回当前登录用户的基本信息及余额数据,需要登录。包括用户 ID、用户名、信任等级、各类余额信息等。
|
description: 返回当前登录用户的基本信息及余额数据,需要登录。包括用户 ID、用户名、信任等级、各类余额信息等。
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ export function FileList() {
|
|||||||
|
|
||||||
// 文件列表查询
|
// 文件列表查询
|
||||||
const listQuery = useQuery({
|
const listQuery = useQuery({
|
||||||
queryKey: ["files", "my", page, pageSize, debouncedKeyword],
|
queryKey: ["files", "all", page, pageSize, debouncedKeyword],
|
||||||
queryFn: () => UploadService.listMyFiles(page, pageSize, debouncedKeyword || undefined),
|
queryFn: () => UploadService.listUploads(page, pageSize, debouncedKeyword || undefined),
|
||||||
})
|
})
|
||||||
|
|
||||||
const files = listQuery.data?.items ?? []
|
const files = listQuery.data?.items ?? []
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ export function FilesMain() {
|
|||||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b pb-5">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b pb-5">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-muted-foreground bg-clip-text text-transparent">
|
<h1 className="text-xl font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-muted-foreground bg-clip-text text-transparent">
|
||||||
文件管理
|
存储管理
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
管理您上传的所有文件,支持下载、数据统计与批量操作
|
管理系统存储的所有文件,支持下载、数据统计与批量操作
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ const data = {
|
|||||||
],
|
],
|
||||||
admin: [
|
admin: [
|
||||||
{ title: "用户管理", url: "/admin/users", icon: UserRound },
|
{ title: "用户管理", url: "/admin/users", icon: UserRound },
|
||||||
{ title: "文件管理", url: "/admin/files", icon: FolderOpen },
|
|
||||||
{ title: "任务管理", url: "/admin/tasks", icon: Layers },
|
{ title: "任务管理", url: "/admin/tasks", icon: Layers },
|
||||||
|
{ title: "存储管理", url: "/admin/files", icon: FolderOpen },
|
||||||
{ title: "数据管理", url: "/admin/database", icon: Database },
|
{ title: "数据管理", url: "/admin/database", icon: Database },
|
||||||
{ title: "系统日志", url: "/admin/logs", icon: Terminal },
|
{ title: "系统日志", url: "/admin/logs", icon: Terminal },
|
||||||
{ title: "系统配置", url: "/admin/system", icon: ShieldCheck },
|
{ title: "系统配置", url: "/admin/system", icon: ShieldCheck },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {BaseService} from '../core/base.service'
|
import {BaseService} from '../core/base.service'
|
||||||
import type {ListUploadsResponse, Upload, UploadImageResponse, FileStatsResponse} from './types'
|
import type {ListUploadsResponse, Upload, UploadImageResponse, FileStatsResponse} from './types'
|
||||||
import type {InternalAxiosRequestConfig} from 'axios'
|
import type {InternalAxiosRequestConfig} from 'axios'
|
||||||
|
import apiClient from '../core/api-client'
|
||||||
|
|
||||||
export type ImageQuality = 'low' | 'medium' | 'high' | 'origin'
|
export type ImageQuality = 'low' | 'medium' | 'high' | 'origin'
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ export function formatFileSize(bytes: number): string {
|
|||||||
* 处理文件上传相关的 API 请求
|
* 处理文件上传相关的 API 请求
|
||||||
*/
|
*/
|
||||||
export class UploadService extends BaseService {
|
export class UploadService extends BaseService {
|
||||||
protected static readonly basePath = '/api/v1/upload'
|
protected static readonly basePath = '/api/v1/admin/uploads'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用文件上传
|
* 通用文件上传
|
||||||
@@ -59,20 +60,21 @@ export class UploadService extends BaseService {
|
|||||||
formData.append('access_mode', String(accessMode))
|
formData.append('access_mode', String(accessMode))
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.post<Upload>('', formData, {
|
const response = await apiClient.post<{ data: Upload }>('/api/v1/upload', formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
} as InternalAxiosRequestConfig)
|
} as InternalAxiosRequestConfig)
|
||||||
|
return response.data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的文件列表
|
* 获取文件列表
|
||||||
* @param page - 页码(1-based)
|
* @param page - 页码(1-based)
|
||||||
* @param pageSize - 每页数量
|
* @param pageSize - 每页数量
|
||||||
* @param keyword - 搜索关键词(文件名模糊)
|
* @param keyword - 搜索关键词(文件名模糊)
|
||||||
* @param type - 业务分类过滤
|
* @param type - 业务分类过滤
|
||||||
* @param extension - 扩展名过滤
|
* @param extension - 扩展名过滤
|
||||||
*/
|
*/
|
||||||
static async listMyFiles(
|
static async listUploads(
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
@@ -83,7 +85,7 @@ export class UploadService extends BaseService {
|
|||||||
if (keyword) params.keyword = keyword
|
if (keyword) params.keyword = keyword
|
||||||
if (type) params.type = type
|
if (type) params.type = type
|
||||||
if (extension) params.extension = extension
|
if (extension) params.extension = extension
|
||||||
return this.get<ListUploadsResponse>('/my', params)
|
return this.get<ListUploadsResponse>('', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,7 +106,7 @@ export class UploadService extends BaseService {
|
|||||||
* 获取单文件下载 URL(触发 attachment 下载)
|
* 获取单文件下载 URL(触发 attachment 下载)
|
||||||
*/
|
*/
|
||||||
static getDownloadUrl(id: string): string {
|
static getDownloadUrl(id: string): string {
|
||||||
return `/api/v1/upload/download/${id}`
|
return `/api/v1/admin/uploads/download/${id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/oauth"
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/util"
|
"github.com/Rain-kl/Wavelet/internal/util"
|
||||||
@@ -18,40 +17,42 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listMyFilesRequest struct {
|
type listFilesRequest struct {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
PageSize int `form:"page_size"`
|
PageSize int `form:"page_size"`
|
||||||
Keyword string `form:"keyword"`
|
Keyword string `form:"keyword"`
|
||||||
Type string `form:"type"`
|
Type string `form:"type"`
|
||||||
Extension string `form:"extension"`
|
Extension string `form:"extension"`
|
||||||
|
UserID uint64 `form:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type listMyFilesResponse struct {
|
type listFilesResponse struct {
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
PageSize int `json:"page_size"`
|
PageSize int `json:"page_size"`
|
||||||
Items []model.Upload `json:"items"`
|
Items []model.Upload `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMyFiles 获取当前用户上传的文件列表
|
// ListFiles 获取系统上传的文件列表
|
||||||
// @Summary 获取我的文件列表
|
// @Summary 获取文件列表
|
||||||
// @Description 分页获取当前登录用户上传的文件,支持文件名关键词、业务类型、扩展名过滤
|
// @Description 分页获取系统上传的文件列表,支持文件名关键词、业务类型、扩展名、上传用户ID过滤
|
||||||
// @Tags upload
|
// @Tags admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param page query int false "页码(默认 1)"
|
// @Param page query int false "页码(默认 1)"
|
||||||
// @Param page_size query int false "每页数量(默认 20,最大 100)"
|
// @Param page_size query int false "每页数量(默认 20,最大 100)"
|
||||||
// @Param keyword query string false "文件名关键词(模糊匹配)"
|
// @Param keyword query string false "文件名关键词(模糊匹配)"
|
||||||
// @Param type query string false "业务分类过滤"
|
// @Param type query string false "业务分类过滤"
|
||||||
// @Param extension query string false "扩展名过滤"
|
// @Param extension query string false "扩展名过滤"
|
||||||
|
// @Param user_id query uint64 false "上传用户 ID"
|
||||||
// @Security SessionCookie
|
// @Security SessionCookie
|
||||||
// @Success 200 {object} util.ResponseAny{data=listMyFilesResponse} "查询成功"
|
// @Success 200 {object} util.ResponseAny{data=listFilesResponse} "查询成功"
|
||||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||||
// @Router /api/v1/upload/my [get]
|
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||||
func ListMyFiles(c *gin.Context) {
|
// @Router /api/v1/admin/uploads [get]
|
||||||
currUser, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
|
func ListFiles(c *gin.Context) {
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
var req listMyFilesRequest
|
var req listFilesRequest
|
||||||
if err := c.ShouldBindQuery(&req); err != nil {
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
c.JSON(http.StatusOK, util.Err(ErrInvalidParams))
|
c.JSON(http.StatusOK, util.Err(ErrInvalidParams))
|
||||||
return
|
return
|
||||||
@@ -64,8 +65,11 @@ func ListMyFiles(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := db.DB(ctx).Model(&model.Upload{}).
|
query := db.DB(ctx).Model(&model.Upload{}).
|
||||||
Where("user_id = ? AND status != ?", currUser.ID, model.UploadStatusDeleted)
|
Where("status != ?", model.UploadStatusDeleted)
|
||||||
|
|
||||||
|
if req.UserID != 0 {
|
||||||
|
query = query.Where("user_id = ?", req.UserID)
|
||||||
|
}
|
||||||
if req.Keyword != "" {
|
if req.Keyword != "" {
|
||||||
query = query.Where("LOWER(file_name) LIKE ?", "%"+strings.ToLower(req.Keyword)+"%")
|
query = query.Where("LOWER(file_name) LIKE ?", "%"+strings.ToLower(req.Keyword)+"%")
|
||||||
}
|
}
|
||||||
@@ -89,7 +93,7 @@ func ListMyFiles(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, util.OK(listMyFilesResponse{
|
c.JSON(http.StatusOK, util.OK(listFilesResponse{
|
||||||
Total: total,
|
Total: total,
|
||||||
Page: req.Page,
|
Page: req.Page,
|
||||||
PageSize: req.PageSize,
|
PageSize: req.PageSize,
|
||||||
@@ -100,16 +104,15 @@ func ListMyFiles(c *gin.Context) {
|
|||||||
// DeleteFile 软删除文件记录
|
// DeleteFile 软删除文件记录
|
||||||
// @Summary 删除文件
|
// @Summary 删除文件
|
||||||
// @Description 将文件状态置为 deleted(软删除),不会立即清理底层存储对象
|
// @Description 将文件状态置为 deleted(软删除),不会立即清理底层存储对象
|
||||||
// @Tags upload
|
// @Tags admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "文件 ID"
|
// @Param id path string true "文件 ID"
|
||||||
// @Security SessionCookie
|
// @Security SessionCookie
|
||||||
// @Success 200 {object} util.ResponseAny "删除成功"
|
// @Success 200 {object} util.ResponseAny "删除成功"
|
||||||
// @Failure 403 {object} util.ResponseAny "无权操作"
|
// @Failure 403 {object} util.ResponseAny "无权操作"
|
||||||
// @Failure 404 {object} util.ResponseAny "文件不存在"
|
// @Failure 404 {object} util.ResponseAny "文件不存在"
|
||||||
// @Router /api/v1/upload/{id} [delete]
|
// @Router /api/v1/admin/uploads/{id} [delete]
|
||||||
func DeleteFile(c *gin.Context) {
|
func DeleteFile(c *gin.Context) {
|
||||||
currUser, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
|
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
if storageReadOnly(ctx) {
|
if storageReadOnly(ctx) {
|
||||||
c.JSON(http.StatusConflict, util.Err(ErrStorageReadOnly))
|
c.JSON(http.StatusConflict, util.Err(ErrStorageReadOnly))
|
||||||
@@ -131,10 +134,6 @@ func DeleteFile(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, util.Err(ErrQueryUploadRecordFailed))
|
c.JSON(http.StatusOK, util.Err(ErrQueryUploadRecordFailed))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if upload.UserID != currUser.ID && !currUser.IsAdmin {
|
|
||||||
c.AbortWithStatus(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := db.DB(ctx).Model(&upload).Update("status", model.UploadStatusDeleted).Error; err != nil {
|
if err := db.DB(ctx).Model(&upload).Update("status", model.UploadStatusDeleted).Error; err != nil {
|
||||||
c.JSON(http.StatusOK, util.Err(ErrDeleteFileFailed))
|
c.JSON(http.StatusOK, util.Err(ErrDeleteFileFailed))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -292,6 +292,9 @@ func checkPrivateFileOwner(c *gin.Context, ownerID uint64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if currUser.IsAdmin {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if currUser.ID != ownerID {
|
if currUser.ID != ownerID {
|
||||||
return errors.New("forbidden: cross-user access denied")
|
return errors.New("forbidden: cross-user access denied")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ func UploadFile(c *gin.Context) {
|
|||||||
// DownloadFile 通用单文件下载接口
|
// DownloadFile 通用单文件下载接口
|
||||||
// @Summary 下载单文件
|
// @Summary 下载单文件
|
||||||
// @Description 根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载
|
// @Description 根据文件 ID 获取文件,以附件形式 (Attachment) 强制开启客户端浏览器下载
|
||||||
// @Tags upload
|
// @Tags admin
|
||||||
// @Produce octet-stream
|
// @Produce octet-stream
|
||||||
// @Param id path string true "文件 ID"
|
// @Param id path string true "文件 ID"
|
||||||
// @Param quality query string false "图片质量 (low, medium, high, origin),默认为 origin"
|
// @Param quality query string false "图片质量 (low, medium, high, origin),默认为 origin"
|
||||||
@@ -198,7 +198,7 @@ func UploadFile(c *gin.Context) {
|
|||||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||||
// @Failure 404 {object} util.ResponseAny "文件不存在"
|
// @Failure 404 {object} util.ResponseAny "文件不存在"
|
||||||
// @Failure 500 {object} util.ResponseAny "服务内部错误"
|
// @Failure 500 {object} util.ResponseAny "服务内部错误"
|
||||||
// @Router /api/v1/upload/download/{id} [get]
|
// @Router /api/v1/admin/uploads/download/{id} [get]
|
||||||
func DownloadFile(c *gin.Context) {
|
func DownloadFile(c *gin.Context) {
|
||||||
upload, err := getUploadRecordByID(c)
|
upload, err := getUploadRecordByID(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -241,15 +241,15 @@ func DownloadFile(c *gin.Context) {
|
|||||||
// BatchDownloadFiles 批量打包 ZIP 下载接口
|
// BatchDownloadFiles 批量打包 ZIP 下载接口
|
||||||
// @Summary 批量打包下载
|
// @Summary 批量打包下载
|
||||||
// @Description 传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突
|
// @Description 传入多个文件 ID,后台实时将其打包压缩为 ZIP 流并输出,自动处理文件名重复冲突
|
||||||
// @Tags upload
|
// @Tags admin
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce octet-stream
|
// @Produce octet-stream
|
||||||
// @Param request body upload.batchDownloadRequest true "包含文件 ID 数组的请求体"
|
// @Param request body upload.batchDownloadRequest true "包含文件 ID 数组 of string 的请求体"
|
||||||
// @Security SessionCookie
|
// @Security SessionCookie
|
||||||
// @Success 200 {file} file "成功下载打包后的 ZIP"
|
// @Success 200 {file} file "成功下载打包后的 ZIP"
|
||||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||||
// @Failure 500 {object} util.ResponseAny "打包失败"
|
// @Failure 500 {object} util.ResponseAny "打包失败"
|
||||||
// @Router /api/v1/upload/download/batch [post]
|
// @Router /api/v1/admin/uploads/download/batch [post]
|
||||||
func BatchDownloadFiles(c *gin.Context) {
|
func BatchDownloadFiles(c *gin.Context) {
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
|||||||
@@ -36,21 +36,30 @@ type testResponse struct {
|
|||||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
uploadGroup := r.Group("/api/v1/upload")
|
|
||||||
|
|
||||||
// Mock authentication middleware
|
authMiddleware := func(c *gin.Context) {
|
||||||
uploadGroup.Use(func(c *gin.Context) {
|
|
||||||
if authUser != nil {
|
if authUser != nil {
|
||||||
util.SetToContext(c, oauth.UserObjKey, authUser)
|
util.SetToContext(c, oauth.UserObjKey, authUser)
|
||||||
}
|
}
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
}
|
||||||
|
|
||||||
|
uploadGroup := r.Group("/api/v1/upload")
|
||||||
|
uploadGroup.Use(authMiddleware)
|
||||||
|
{
|
||||||
|
uploadGroup.POST("", UploadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminGroup := r.Group("/api/v1/admin/uploads")
|
||||||
|
adminGroup.Use(authMiddleware)
|
||||||
|
{
|
||||||
|
adminGroup.GET("", ListFiles)
|
||||||
|
adminGroup.GET("/stats", GetFileStats)
|
||||||
|
adminGroup.DELETE("/:id", DeleteFile)
|
||||||
|
adminGroup.GET("/download/:id", DownloadFile)
|
||||||
|
adminGroup.POST("/download/batch", BatchDownloadFiles)
|
||||||
|
}
|
||||||
|
|
||||||
uploadGroup.POST("", UploadFile)
|
|
||||||
uploadGroup.GET("/my", ListMyFiles)
|
|
||||||
uploadGroup.GET("/stats", GetFileStats)
|
|
||||||
uploadGroup.GET("/download/:id", DownloadFile)
|
|
||||||
uploadGroup.POST("/download/batch", BatchDownloadFiles)
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +365,7 @@ func TestDownloadFile(t *testing.T) {
|
|||||||
dbConn.Create(&localUpload)
|
dbConn.Create(&localUpload)
|
||||||
|
|
||||||
t.Run("download file successfully", func(t *testing.T) {
|
t.Run("download file successfully", func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("GET", "/api/v1/upload/download/2001", nil)
|
req, _ := http.NewRequest("GET", "/api/v1/admin/uploads/download/2001", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -381,7 +390,7 @@ func TestDownloadFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("download non-existent file", func(t *testing.T) {
|
t.Run("download non-existent file", func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("GET", "/api/v1/upload/download/9999", nil)
|
req, _ := http.NewRequest("GET", "/api/v1/admin/uploads/download/9999", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -391,7 +400,7 @@ func TestDownloadFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListMyFiles(t *testing.T) {
|
func TestListFiles(t *testing.T) {
|
||||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
@@ -451,7 +460,7 @@ func TestListMyFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("returns requested page", func(t *testing.T) {
|
t.Run("returns requested page", func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("GET", "/api/v1/upload/my?page=2&page_size=2", nil)
|
req, _ := http.NewRequest("GET", "/api/v1/admin/uploads?page=2&page_size=2", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -460,29 +469,29 @@ func TestListMyFiles(t *testing.T) {
|
|||||||
t.Fatalf("failed to parse response: %v", err)
|
t.Fatalf("failed to parse response: %v", err)
|
||||||
}
|
}
|
||||||
if resp.ErrorMsg != "" {
|
if resp.ErrorMsg != "" {
|
||||||
t.Fatalf("ListMyFiles() error = %q, want empty", resp.ErrorMsg)
|
t.Fatalf("ListFiles() error = %q, want empty", resp.ErrorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
var got listMyFilesResponse
|
var got listFilesResponse
|
||||||
if err := json.Unmarshal(resp.Data, &got); err != nil {
|
if err := json.Unmarshal(resp.Data, &got); err != nil {
|
||||||
t.Fatalf("failed to parse list response: %v", err)
|
t.Fatalf("failed to parse list response: %v", err)
|
||||||
}
|
}
|
||||||
if got.Page != 2 {
|
if got.Page != 2 {
|
||||||
t.Errorf("ListMyFiles(page=2).Page = %d, want 2", got.Page)
|
t.Errorf("ListFiles(page=2).Page = %d, want 2", got.Page)
|
||||||
}
|
}
|
||||||
if got.PageSize != 2 {
|
if got.PageSize != 2 {
|
||||||
t.Errorf("ListMyFiles(page_size=2).PageSize = %d, want 2", got.PageSize)
|
t.Errorf("ListFiles(page_size=2).PageSize = %d, want 2", got.PageSize)
|
||||||
}
|
}
|
||||||
if got.Total != 3 {
|
if got.Total != 4 {
|
||||||
t.Errorf("ListMyFiles().Total = %d, want 3", got.Total)
|
t.Errorf("ListFiles().Total = %d, want 4", got.Total)
|
||||||
}
|
}
|
||||||
if len(got.Items) != 1 {
|
if len(got.Items) != 2 {
|
||||||
t.Fatalf("ListMyFiles(page=2, page_size=2) returned %d items, want 1", len(got.Items))
|
t.Fatalf("ListFiles(page=2, page_size=2) returned %d items, want 2", len(got.Items))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("filters filename case insensitively", func(t *testing.T) {
|
t.Run("filters filename case insensitively", func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("GET", "/api/v1/upload/my?keyword=photo", nil)
|
req, _ := http.NewRequest("GET", "/api/v1/admin/uploads?keyword=photo", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -491,21 +500,46 @@ func TestListMyFiles(t *testing.T) {
|
|||||||
t.Fatalf("failed to parse response: %v", err)
|
t.Fatalf("failed to parse response: %v", err)
|
||||||
}
|
}
|
||||||
if resp.ErrorMsg != "" {
|
if resp.ErrorMsg != "" {
|
||||||
t.Fatalf("ListMyFiles(keyword=photo) error = %q, want empty", resp.ErrorMsg)
|
t.Fatalf("ListFiles(keyword=photo) error = %q, want empty", resp.ErrorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
var got listMyFilesResponse
|
var got listFilesResponse
|
||||||
if err := json.Unmarshal(resp.Data, &got); err != nil {
|
if err := json.Unmarshal(resp.Data, &got); err != nil {
|
||||||
t.Fatalf("failed to parse list response: %v", err)
|
t.Fatalf("failed to parse list response: %v", err)
|
||||||
}
|
}
|
||||||
if got.Total != 1 {
|
if got.Total != 1 {
|
||||||
t.Errorf("ListMyFiles(keyword=photo).Total = %d, want 1", got.Total)
|
t.Errorf("ListFiles(keyword=photo).Total = %d, want 1", got.Total)
|
||||||
}
|
}
|
||||||
if len(got.Items) != 1 {
|
if len(got.Items) != 1 {
|
||||||
t.Fatalf("ListMyFiles(keyword=photo) returned %d items, want 1", len(got.Items))
|
t.Fatalf("ListFiles(keyword=photo) returned %d items, want 1", len(got.Items))
|
||||||
}
|
}
|
||||||
if got.Items[0].FileName != "Second-Photo.PNG" {
|
if got.Items[0].FileName != "Second-Photo.PNG" {
|
||||||
t.Errorf("ListMyFiles(keyword=photo).Items[0].FileName = %q, want %q", got.Items[0].FileName, "Second-Photo.PNG")
|
t.Errorf("ListFiles(keyword=photo).Items[0].FileName = %q, want %q", got.Items[0].FileName, "Second-Photo.PNG")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("filters by user_id", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("GET", "/api/v1/admin/uploads?user_id=1001", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
var resp testResponse
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("failed to parse response: %v", err)
|
||||||
|
}
|
||||||
|
if resp.ErrorMsg != "" {
|
||||||
|
t.Fatalf("ListFiles(user_id=1001) error = %q, want empty", resp.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var got listFilesResponse
|
||||||
|
if err := json.Unmarshal(resp.Data, &got); err != nil {
|
||||||
|
t.Fatalf("failed to parse list response: %v", err)
|
||||||
|
}
|
||||||
|
if got.Total != 3 {
|
||||||
|
t.Errorf("ListFiles(user_id=1001).Total = %d, want 3", got.Total)
|
||||||
|
}
|
||||||
|
if len(got.Items) != 3 {
|
||||||
|
t.Fatalf("ListFiles(user_id=1001) returned %d items, want 3", len(got.Items))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -573,7 +607,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
|||||||
reqBody, _ := json.Marshal(batchDownloadRequest{
|
reqBody, _ := json.Marshal(batchDownloadRequest{
|
||||||
IDs: []string{"3001", "3002", "3003"},
|
IDs: []string{"3001", "3002", "3003"},
|
||||||
})
|
})
|
||||||
req, _ := http.NewRequest("POST", "/api/v1/upload/download/batch", bytes.NewReader(reqBody))
|
req, _ := http.NewRequest("POST", "/api/v1/admin/uploads/download/batch", bytes.NewReader(reqBody))
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -688,7 +722,7 @@ func TestUploadAccessModeAccessControl(t *testing.T) {
|
|||||||
|
|
||||||
// 3. Verify accessing private file as user1 (owner) succeeds
|
// 3. Verify accessing private file as user1 (owner) succeeds
|
||||||
wAccessOwner := httptest.NewRecorder()
|
wAccessOwner := httptest.NewRecorder()
|
||||||
reqAccessOwner, _ := http.NewRequest("GET", "/api/v1/upload/download/"+strconv.FormatUint(upload1.ID, 10), nil)
|
reqAccessOwner, _ := http.NewRequest("GET", "/api/v1/admin/uploads/download/"+strconv.FormatUint(upload1.ID, 10), nil)
|
||||||
router.ServeHTTP(wAccessOwner, reqAccessOwner)
|
router.ServeHTTP(wAccessOwner, reqAccessOwner)
|
||||||
if wAccessOwner.Code != http.StatusOK {
|
if wAccessOwner.Code != http.StatusOK {
|
||||||
t.Errorf("owner should be allowed to download private file, got status %d", wAccessOwner.Code)
|
t.Errorf("owner should be allowed to download private file, got status %d", wAccessOwner.Code)
|
||||||
@@ -697,7 +731,7 @@ func TestUploadAccessModeAccessControl(t *testing.T) {
|
|||||||
// 4. Verify accessing private file as user2 (non-owner) fails
|
// 4. Verify accessing private file as user2 (non-owner) fails
|
||||||
routerUser2 := setupTestRouter(user2)
|
routerUser2 := setupTestRouter(user2)
|
||||||
wAccessOther := httptest.NewRecorder()
|
wAccessOther := httptest.NewRecorder()
|
||||||
reqAccessOther, _ := http.NewRequest("GET", "/api/v1/upload/download/"+strconv.FormatUint(upload1.ID, 10), nil)
|
reqAccessOther, _ := http.NewRequest("GET", "/api/v1/admin/uploads/download/"+strconv.FormatUint(upload1.ID, 10), nil)
|
||||||
routerUser2.ServeHTTP(wAccessOther, reqAccessOther)
|
routerUser2.ServeHTTP(wAccessOther, reqAccessOther)
|
||||||
if wAccessOther.Code != http.StatusUnauthorized {
|
if wAccessOther.Code != http.StatusUnauthorized {
|
||||||
t.Errorf("non-owner should be denied download of private file, got status %d, want 401", wAccessOther.Code)
|
t.Errorf("non-owner should be denied download of private file, got status %d, want 401", wAccessOther.Code)
|
||||||
@@ -705,7 +739,7 @@ func TestUploadAccessModeAccessControl(t *testing.T) {
|
|||||||
|
|
||||||
// 5. Verify accessing public file as user2 (non-owner) succeeds
|
// 5. Verify accessing public file as user2 (non-owner) succeeds
|
||||||
wAccessPublic := httptest.NewRecorder()
|
wAccessPublic := httptest.NewRecorder()
|
||||||
reqAccessPublic, _ := http.NewRequest("GET", "/api/v1/upload/download/"+strconv.FormatUint(upload2.ID, 10), nil)
|
reqAccessPublic, _ := http.NewRequest("GET", "/api/v1/admin/uploads/download/"+strconv.FormatUint(upload2.ID, 10), nil)
|
||||||
routerUser2.ServeHTTP(wAccessPublic, reqAccessPublic)
|
routerUser2.ServeHTTP(wAccessPublic, reqAccessPublic)
|
||||||
if wAccessPublic.Code != http.StatusOK {
|
if wAccessPublic.Code != http.StatusOK {
|
||||||
t.Errorf("any logged-in user should be allowed to download public file, got status %d", wAccessPublic.Code)
|
t.Errorf("any logged-in user should be allowed to download public file, got status %d", wAccessPublic.Code)
|
||||||
@@ -768,7 +802,7 @@ func TestGetFileStats(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/api/v1/upload/stats", nil)
|
req, _ := http.NewRequest("GET", "/api/v1/admin/uploads/stats", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/oauth"
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/util"
|
"github.com/Rain-kl/Wavelet/internal/util"
|
||||||
@@ -35,20 +34,19 @@ type fileStatsResponse struct {
|
|||||||
Types []distributionItem `json:"types"`
|
Types []distributionItem `json:"types"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFileStats 获取当前登录用户的文件统计数据
|
// GetFileStats 获取系统上传的文件统计数据
|
||||||
// @Summary 获取文件统计数据
|
// @Summary 获取文件统计数据
|
||||||
// @Description 返回当前用户的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据
|
// @Description 返回系统级的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据
|
||||||
// @Tags upload
|
// @Tags admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security SessionCookie
|
// @Security SessionCookie
|
||||||
// @Success 200 {object} util.ResponseAny{data=fileStatsResponse} "获取成功"
|
// @Success 200 {object} util.ResponseAny{data=fileStatsResponse} "获取成功"
|
||||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||||
|
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||||
// @Router /api/v1/upload/stats [get]
|
// @Router /api/v1/admin/uploads/stats [get]
|
||||||
func GetFileStats(c *gin.Context) {
|
func GetFileStats(c *gin.Context) {
|
||||||
currUser, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
|
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
userID := currUser.ID
|
|
||||||
|
|
||||||
// 1. 获取总文件数与总文件大小
|
// 1. 获取总文件数与总文件大小
|
||||||
var summary struct {
|
var summary struct {
|
||||||
@@ -57,7 +55,7 @@ func GetFileStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err := db.DB(ctx).Model(&model.Upload{}).
|
err := db.DB(ctx).Model(&model.Upload{}).
|
||||||
Select("COUNT(*) as total_count, COALESCE(SUM(file_size), 0) as total_size").
|
Select("COUNT(*) as total_count, COALESCE(SUM(file_size), 0) as total_size").
|
||||||
Where("user_id = ? AND status != ?", userID, model.UploadStatusDeleted).
|
Where("status != ?", model.UploadStatusDeleted).
|
||||||
Scan(&summary).Error
|
Scan(&summary).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, util.Err(err.Error()))
|
c.JSON(http.StatusOK, util.Err(err.Error()))
|
||||||
@@ -73,7 +71,7 @@ func GetFileStats(c *gin.Context) {
|
|||||||
var typeRaw []rawDist
|
var typeRaw []rawDist
|
||||||
err = db.DB(ctx).Model(&model.Upload{}).
|
err = db.DB(ctx).Model(&model.Upload{}).
|
||||||
Select("type as key, COUNT(*) as count, COALESCE(SUM(file_size), 0) as size").
|
Select("type as key, COUNT(*) as count, COALESCE(SUM(file_size), 0) as size").
|
||||||
Where("user_id = ? AND status != ?", userID, model.UploadStatusDeleted).
|
Where("status != ?", model.UploadStatusDeleted).
|
||||||
Group("type").
|
Group("type").
|
||||||
Scan(&typeRaw).Error
|
Scan(&typeRaw).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,7 +101,7 @@ func GetFileStats(c *gin.Context) {
|
|||||||
var fileRaws []fileCategoryRaw
|
var fileRaws []fileCategoryRaw
|
||||||
err = db.DB(ctx).Model(&model.Upload{}).
|
err = db.DB(ctx).Model(&model.Upload{}).
|
||||||
Select("extension, mime_type, file_size").
|
Select("extension, mime_type, file_size").
|
||||||
Where("user_id = ? AND status != ?", userID, model.UploadStatusDeleted).
|
Where("status != ?", model.UploadStatusDeleted).
|
||||||
Scan(&fileRaws).Error
|
Scan(&fileRaws).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, util.Err(err.Error()))
|
c.JSON(http.StatusOK, util.Err(err.Error()))
|
||||||
@@ -144,7 +142,7 @@ func GetFileStats(c *gin.Context) {
|
|||||||
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -6)
|
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -6)
|
||||||
err = db.DB(ctx).Model(&model.Upload{}).
|
err = db.DB(ctx).Model(&model.Upload{}).
|
||||||
Select("created_at, file_size").
|
Select("created_at, file_size").
|
||||||
Where("user_id = ? AND status != ? AND created_at >= ?", userID, model.UploadStatusDeleted, startTime).
|
Where("status != ? AND created_at >= ?", model.UploadStatusDeleted, startTime).
|
||||||
Scan(&trendRaws).Error
|
Scan(&trendRaws).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, util.Err(err.Error()))
|
c.JSON(http.StatusOK, util.Err(err.Error()))
|
||||||
|
|||||||
@@ -214,11 +214,6 @@ func registerRoutes(r *gin.Engine) {
|
|||||||
uploadRouter.Use(oauth.LoginRequired())
|
uploadRouter.Use(oauth.LoginRequired())
|
||||||
{
|
{
|
||||||
uploadRouter.POST("", upload.UploadFile)
|
uploadRouter.POST("", upload.UploadFile)
|
||||||
uploadRouter.GET("/my", upload.ListMyFiles)
|
|
||||||
uploadRouter.GET("/stats", upload.GetFileStats)
|
|
||||||
uploadRouter.DELETE("/:id", upload.DeleteFile)
|
|
||||||
uploadRouter.GET("/download/:id", upload.DownloadFile)
|
|
||||||
uploadRouter.POST("/download/batch", upload.BatchDownloadFiles)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config (public)
|
// Config (public)
|
||||||
@@ -282,7 +277,15 @@ func registerRoutes(r *gin.Engine) {
|
|||||||
adminRouter.DELETE("/users/:id", admin_user.DeleteUser)
|
adminRouter.DELETE("/users/:id", admin_user.DeleteUser)
|
||||||
|
|
||||||
// Uploads
|
// Uploads
|
||||||
adminRouter.GET("/uploads/types", upload.GetDistinctUploadTypes)
|
adminUploadsRouter := adminRouter.Group("/uploads")
|
||||||
|
{
|
||||||
|
adminUploadsRouter.GET("", upload.ListFiles)
|
||||||
|
adminUploadsRouter.GET("/stats", upload.GetFileStats)
|
||||||
|
adminUploadsRouter.DELETE("/:id", upload.DeleteFile)
|
||||||
|
adminUploadsRouter.GET("/download/:id", upload.DownloadFile)
|
||||||
|
adminUploadsRouter.POST("/download/batch", upload.BatchDownloadFiles)
|
||||||
|
adminUploadsRouter.GET("/types", upload.GetDistinctUploadTypes)
|
||||||
|
}
|
||||||
|
|
||||||
// System Config
|
// System Config
|
||||||
adminRouter.POST("/system-configs", system_config.CreateSystemConfig)
|
adminRouter.POST("/system-configs", system_config.CreateSystemConfig)
|
||||||
|
|||||||
Reference in New Issue
Block a user