feat: add file upload implement to v2 api

This commit is contained in:
CorrectRoadH 2023-12-21 14:44:14 +08:00
parent 6f722b3506
commit 6c2b814306
4 changed files with 310 additions and 2 deletions

View File

@ -90,6 +90,107 @@ paths:
$ref: "#/components/responses/ResponseOK"
"500":
$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:
get:
tags:
@ -151,6 +252,20 @@ components:
schema:
$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:
description: Internal Server Error
content:
@ -196,6 +311,14 @@ components:
type: string
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:
properties:
running:

View File

@ -3,6 +3,7 @@ package v2
import (
"net/http"
"github.com/IceWhaleTech/CasaOS/codegen"
"github.com/labstack/echo/v4"
)
@ -15,3 +16,11 @@ func (s *CasaOS) GetFileTest(ctx echo.Context) error {
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)
}

View File

@ -2,10 +2,15 @@ package v2
import (
"github.com/IceWhaleTech/CasaOS/codegen"
"github.com/IceWhaleTech/CasaOS/service"
)
type CasaOS struct{}
type CasaOS struct {
fileUploadService *service.FileUploadService
}
func NewCasaOS() codegen.ServerInterface {
return &CasaOS{}
return &CasaOS{
fileUploadService: service.NewFileUploadService(),
}
}

171
service/file_upload.go Normal file
View 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)
}