mirror of
https://github.com/IceWhaleTech/CasaOS.git
synced 2025-12-03 19:56:17 +00:00
feat: add file upload implement to v2 api
This commit is contained in:
parent
6f722b3506
commit
6c2b814306
@ -90,6 +90,107 @@ paths:
|
|||||||
$ref: "#/components/responses/ResponseOK"
|
$ref: "#/components/responses/ResponseOK"
|
||||||
"500":
|
"500":
|
||||||
$ref: "#/components/responses/ResponseInternalServerError"
|
$ref: "#/components/responses/ResponseInternalServerError"
|
||||||
|
|
||||||
|
/file/upload:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- File
|
||||||
|
summary: Check upload chunk
|
||||||
|
parameters:
|
||||||
|
- name: path
|
||||||
|
in: query
|
||||||
|
description: File path
|
||||||
|
required: true
|
||||||
|
example: "/DATA/test.log"
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: relativePath
|
||||||
|
in: query
|
||||||
|
description: File path
|
||||||
|
required: true
|
||||||
|
example: "/DATA/test.log"
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: filename
|
||||||
|
in: query
|
||||||
|
description: File name
|
||||||
|
required: true
|
||||||
|
example: "test.log"
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: chunkNumber
|
||||||
|
in: query
|
||||||
|
description: chunk number
|
||||||
|
required: true
|
||||||
|
example: 1
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: totalChunks
|
||||||
|
in: query
|
||||||
|
description: total chunks
|
||||||
|
example: 2
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Check if the file block has been uploaded (needs to be modified later)
|
||||||
|
operationId: checkUploadChunk
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: "#/components/responses/ResponseStringOK"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ResponseClientError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/ResponseInternalServerError"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- File
|
||||||
|
summary: Upload file
|
||||||
|
description: Upload file
|
||||||
|
operationId: postUploadFile
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
relativePath:
|
||||||
|
type: string
|
||||||
|
example: "/DATA/test.log"
|
||||||
|
filename:
|
||||||
|
type: string
|
||||||
|
example: "/DATA/test2.log"
|
||||||
|
totalChunks:
|
||||||
|
type: string
|
||||||
|
example: "2"
|
||||||
|
chunkNumber:
|
||||||
|
type: string
|
||||||
|
example: "20"
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
example: "/DATA"
|
||||||
|
file:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
chunkSize:
|
||||||
|
type: string
|
||||||
|
example: "1024"
|
||||||
|
currentChunkSize:
|
||||||
|
type: string
|
||||||
|
example: "1024"
|
||||||
|
totalSize:
|
||||||
|
type: string
|
||||||
|
example: "1024"
|
||||||
|
identifier:
|
||||||
|
type: string
|
||||||
|
example: "test.log"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: "#/components/responses/ResponseStringOK"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ResponseClientError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/ResponseInternalServerError"
|
||||||
|
|
||||||
/zt/info:
|
/zt/info:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -151,6 +252,20 @@ components:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BaseResponse"
|
$ref: "#/components/schemas/BaseResponse"
|
||||||
|
|
||||||
|
ResponseStringOK:
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/SuccessResponseString"
|
||||||
|
|
||||||
|
ResponseClientError:
|
||||||
|
description: Client Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BaseResponse"
|
||||||
|
|
||||||
ResponseInternalServerError:
|
ResponseInternalServerError:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
content:
|
content:
|
||||||
@ -195,6 +310,14 @@ components:
|
|||||||
description: message returned by server side if there is any
|
description: message returned by server side if there is any
|
||||||
type: string
|
type: string
|
||||||
example: ""
|
example: ""
|
||||||
|
|
||||||
|
SuccessResponseString:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/BaseResponse"
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
description: When the interface returns success, this field is the specific success information
|
||||||
|
|
||||||
HealthServices:
|
HealthServices:
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package v2
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/IceWhaleTech/CasaOS/codegen"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,3 +16,11 @@ func (s *CasaOS) GetFileTest(ctx echo.Context) error {
|
|||||||
|
|
||||||
return ctx.String(200, "pong")
|
return ctx.String(200, "pong")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CasaOS) CheckUploadChunk(ctx echo.Context, params codegen.CheckUploadChunkParams) error {
|
||||||
|
return c.fileUploadService.TestChunk(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CasaOS) PostUploadFile(ctx echo.Context) error {
|
||||||
|
return c.fileUploadService.UploadFile(ctx)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,15 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/IceWhaleTech/CasaOS/codegen"
|
"github.com/IceWhaleTech/CasaOS/codegen"
|
||||||
|
"github.com/IceWhaleTech/CasaOS/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CasaOS struct{}
|
type CasaOS struct {
|
||||||
|
fileUploadService *service.FileUploadService
|
||||||
|
}
|
||||||
|
|
||||||
func NewCasaOS() codegen.ServerInterface {
|
func NewCasaOS() codegen.ServerInterface {
|
||||||
return &CasaOS{}
|
return &CasaOS{
|
||||||
|
fileUploadService: service.NewFileUploadService(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
171
service/file_upload.go
Normal file
171
service/file_upload.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
init bool
|
||||||
|
uploaded []bool
|
||||||
|
uploadedChunkNum int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileUploadService struct {
|
||||||
|
uploadStatus sync.Map
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileUploadService() *FileUploadService {
|
||||||
|
return &FileUploadService{
|
||||||
|
uploadStatus: sync.Map{},
|
||||||
|
lock: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileUploadService) TestChunk(c echo.Context) error {
|
||||||
|
// s.lock.RLock()
|
||||||
|
// defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
identifier := c.QueryParam("identifier")
|
||||||
|
chunkNumber, err := strconv.ParseInt(c.QueryParam("chunkNumber"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileInfoTemp, ok := s.uploadStatus.Load(identifier)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return c.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo := fileInfoTemp.(*FileInfo)
|
||||||
|
|
||||||
|
if !fileInfo.init {
|
||||||
|
return c.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里返回的应该得是 permanentErrors,不是 404. 不然前端会上传失败而不是重传块。
|
||||||
|
// 梁哥应该得改一下。
|
||||||
|
if !fileInfo.uploaded[chunkNumber-1] {
|
||||||
|
return c.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.NoContent(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileUploadService) UploadFile(c echo.Context) error {
|
||||||
|
path := filepath.Join(c.FormValue("path"), c.FormValue("relativePath"))
|
||||||
|
|
||||||
|
// handle the request
|
||||||
|
chunkNumber, err := strconv.ParseInt(c.FormValue("chunkNumber"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
chunkSize, err := strconv.ParseInt(c.FormValue("chunkSize"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
currentChunkSize, err := strconv.ParseInt(c.FormValue("currentChunkSize"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
totalChunks, err := strconv.ParseInt(c.FormValue("totalChunks"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
totalSize, err := strconv.ParseInt(c.FormValue("totalSize"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
identifier := c.FormValue("identifier")
|
||||||
|
fileName := c.FormValue("filename")
|
||||||
|
bin, err := c.FormFile("file")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
s.lock.Lock()
|
||||||
|
fileInfoTemp, ok := s.uploadStatus.Load(identifier)
|
||||||
|
var fileInfo *FileInfo
|
||||||
|
|
||||||
|
file, err := os.OpenFile(path+"/"+fileName+".tmp", os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// file, err := os.Create(path + "/" + fileName + ".tmp")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre allocate file size
|
||||||
|
fmt.Println("truncate", totalSize)
|
||||||
|
if err != nil {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// file info init
|
||||||
|
fileInfo = &FileInfo{
|
||||||
|
init: true,
|
||||||
|
uploaded: make([]bool, totalChunks),
|
||||||
|
uploadedChunkNum: 0,
|
||||||
|
}
|
||||||
|
s.uploadStatus.Store(identifier, fileInfo)
|
||||||
|
} else {
|
||||||
|
fileInfo = fileInfoTemp.(*FileInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
|
_, err = file.Seek((chunkNumber-1)*chunkSize, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := bin.Open()
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, int(currentChunkSize))
|
||||||
|
_, err = io.CopyBuffer(file, src, buf)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.lock.Lock()
|
||||||
|
// handle file after write a chunk
|
||||||
|
// handle single chunk upload twice
|
||||||
|
if !fileInfo.uploaded[chunkNumber-1] {
|
||||||
|
fileInfo.uploadedChunkNum++
|
||||||
|
fileInfo.uploaded[chunkNumber-1] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle file after write all chunk
|
||||||
|
if fileInfo.uploadedChunkNum == totalChunks {
|
||||||
|
file.Close()
|
||||||
|
os.Rename(path+"/"+fileName+".tmp", path+"/"+fileName)
|
||||||
|
|
||||||
|
// remove upload status info after upload complete
|
||||||
|
s.uploadStatus.Delete(identifier)
|
||||||
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
|
return c.NoContent(http.StatusOK)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user