2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 12:40:25 +08:00

fix(文件管理): 一堆小问题

This commit is contained in:
耗子
2024-03-15 00:39:01 +08:00
parent 71903c1100
commit deb9d9fbd2
20 changed files with 211 additions and 82 deletions

View File

@@ -1,11 +1,14 @@
package controllers
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
requests "panel/app/http/requests/file"
"panel/pkg/tools"
@@ -47,13 +50,7 @@ func (r *FileController) Create(ctx http.Context) http.Response {
}
}
if err := tools.Chmod(request.Path, 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Chown(request.Path, "www", "www"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.Path, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -75,6 +72,17 @@ func (r *FileController) Content(ctx http.Context) http.Response {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if fileInfo.IsDir() {
return Error(ctx, http.StatusInternalServerError, "目标路径不是文件")
}
if fileInfo.Size() > 10*1024*1024 {
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10M")
}
content, err := tools.Read(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
@@ -91,27 +99,26 @@ func (r *FileController) Content(ctx http.Context) http.Response {
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Exist true "request"
// @Param data body requests.Save true "request"
// @Param content body string true "content"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/save [post]
func (r *FileController) Save(ctx http.Context) http.Response {
var request requests.Exist
var request requests.Save
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
content := ctx.Request().Input("content")
fileInfo, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, content, fileInfo.Mode()); err != nil {
if err = tools.Write(request.Path, request.Content, fileInfo.Mode()); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.Path, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -148,35 +155,37 @@ func (r *FileController) Delete(ctx http.Context) http.Response {
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.NotExist true "request"
// @Param file formData file true "file"
// @Param file formData file true "file"
// @Param path formData string true "path"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/upload [post]
func (r *FileController) Upload(ctx http.Context) http.Response {
var request requests.NotExist
var request requests.Upload
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
pathInfo, err := tools.FileInfo(request.Path)
src, err := request.File.Open()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if !pathInfo.IsDir() {
return Error(ctx, http.StatusInternalServerError, "目标路径不是目录")
defer src.Close()
if tools.Exists(request.Path) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
}
file, err := ctx.Request().File("file")
if err != nil {
return Error(ctx, http.StatusInternalServerError, "上传文件失败")
}
_, err = file.Store(request.Path)
data, err := io.ReadAll(src)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, string(data), 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.Path, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -199,13 +208,14 @@ func (r *FileController) Move(ctx http.Context) http.Response {
}
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusInternalServerError, "目标路径已存在,是否覆盖?")
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
}
if err := tools.Mv(request.Old, request.New); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.New, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -228,13 +238,14 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
}
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusInternalServerError, "目标路径已存在,是否覆盖?")
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
}
if err := tools.Cp(request.Old, request.New); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.New, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -250,7 +261,7 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/file/download [get]
func (r *FileController) Download(ctx http.Context) http.Response {
var request requests.NotExist
var request requests.Exist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
@@ -312,7 +323,14 @@ func (r *FileController) Info(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, fileInfo)
return Success(ctx, http.Json{
"name": fileInfo.Name(),
"size": tools.FormatBytes(float64(fileInfo.Size())),
"mode_str": fileInfo.Mode().String(),
"mode": fmt.Sprintf("%04o", fileInfo.Mode().Perm()),
"dir": fileInfo.IsDir(),
"modify": carbon.FromStdTime(fileInfo.ModTime()).ToDateTimeString(),
})
}
// Permission
@@ -336,6 +354,9 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
if err := tools.Chmod(request.Path, os.FileMode(request.Mode)); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Chown(request.Path, request.Owner, request.Group); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
@@ -362,6 +383,7 @@ func (r *FileController) Archive(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.File, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -387,6 +409,7 @@ func (r *FileController) UnArchive(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.Path, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -441,17 +464,29 @@ func (r *FileController) List(ctx http.Context) http.Response {
return sanitize
}
paths := make(map[string]os.FileInfo)
err := filepath.Walk(request.Path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
paths[path] = info
return nil
})
fileInfoList, err := os.ReadDir(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
var paths []any
for _, fileInfo := range fileInfoList {
info, _ := fileInfo.Info()
paths = append(paths, map[string]any{
"name": info.Name(),
"size": tools.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"dir": info.IsDir(),
"modify": carbon.FromStdTime(info.ModTime()).ToDateTimeString(),
})
}
return Success(ctx, paths)
}
// setPermission
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
_ = tools.Chmod(path, os.FileMode(mode))
_ = tools.Chown(path, owner, group)
}

View File

@@ -17,8 +17,8 @@ func (r *Archive) Authorize(ctx http.Context) error {
func (r *Archive) Rules(ctx http.Context) map[string]string {
return map[string]string{
"paths": "array",
"paths.*": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"file": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_not_exists",
"paths.*": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"file": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
}
}

View File

@@ -16,8 +16,8 @@ func (r *Copy) Authorize(ctx http.Context) error {
func (r *Copy) Rules(ctx http.Context) map[string]string {
return map[string]string{
"old": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"new": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$",
"old": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"new": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
}
}

View File

@@ -15,7 +15,7 @@ func (r *Exist) Authorize(ctx http.Context) error {
func (r *Exist) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
}
}

View File

@@ -16,8 +16,8 @@ func (r *Move) Authorize(ctx http.Context) error {
func (r *Move) Rules(ctx http.Context) map[string]string {
return map[string]string{
"old": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"new": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_not_exists",
"old": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"new": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
}
}

View File

@@ -15,7 +15,7 @@ func (r *NotExist) Authorize(ctx http.Context) error {
func (r *NotExist) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_not_exists",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
}
}

View File

@@ -18,7 +18,7 @@ func (r *Permission) Authorize(ctx http.Context) error {
func (r *Permission) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"mode": "regex:^[0-7]{3}$|uint",
"owner": "regex:^[a-zA-Z0-9_-]+$",
"group": "regex:^[a-zA-Z0-9_-]+$",

View File

@@ -0,0 +1,34 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Save struct {
Path string `form:"path" json:"path"`
Content string `form:"content" json:"content"`
}
func (r *Save) Authorize(ctx http.Context) error {
return nil
}
func (r *Save) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"content": "required|string",
}
}
func (r *Save) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Save) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Save) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}

View File

@@ -16,7 +16,7 @@ func (r *Search) Authorize(ctx http.Context) error {
func (r *Search) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"keyword": "required|string",
}
}

View File

@@ -16,8 +16,8 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
return map[string]string{
"file": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_exists",
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$|path_not_exists",
"file": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
}
}

View File

@@ -0,0 +1,36 @@
package requests
import (
"mime/multipart"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Upload struct {
Path string `form:"path" json:"path"`
File *multipart.FileHeader `form:"file" json:"file"`
}
func (r *Upload) Authorize(ctx http.Context) error {
return nil
}
func (r *Upload) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"file": "required",
}
}
func (r *Upload) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Upload) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Upload) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}

View File

@@ -21,7 +21,7 @@ func (r *Create) Authorize(ctx http.Context) error {
func (r *Create) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|regex:^[a-zA-Z0-9-_]+$",
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",

View File

@@ -21,7 +21,7 @@ func (r *Update) Authorize(ctx http.Context) error {
func (r *Update) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|regex:^[a-zA-Z0-9-_]+$",
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",

View File

@@ -27,7 +27,7 @@ func (r *Add) Rules(ctx http.Context) map[string]string {
"name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$|not_exists:websites,name|not_in:phpmyadmin,mysql,panel,ssh",
"domains": "required|slice",
"ports": "required|slice",
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"php": "required",
"db": "bool",
"db_type": "required_if:db,true|in:0,mysql,postgresql",

View File

@@ -32,5 +32,5 @@ func (receiver *PathExists) Passes(_ validation.Data, val any, options ...any) b
// Message Get the validation error message.
func (receiver *PathExists) Message() string {
return "路径 %v 不存在"
return "路径不存在"
}

View File

@@ -11,7 +11,7 @@ type PathNotExists struct {
// Signature The name of the rule.
func (receiver *PathNotExists) Signature() string {
return "path_exists"
return "path_not_exists"
}
// Passes Determine if the validation rule passes.
@@ -32,5 +32,5 @@ func (receiver *PathNotExists) Passes(_ validation.Data, val any, options ...any
// Message Get the validation error message.
func (receiver *PathNotExists) Message() string {
return "路径 %v 已存在"
return "路径已存在"
}

View File

@@ -1258,7 +1258,7 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.Exist"
"$ref": "#/definitions/requests.Save"
}
},
{
@@ -1373,21 +1373,19 @@ const docTemplate = `{
],
"summary": "上传文件",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.NotExist"
}
},
{
"type": "file",
"description": "file",
"name": "file",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "path",
"name": "path",
"in": "formData",
"required": true
}
],
"responses": {
@@ -3071,6 +3069,17 @@ const docTemplate = `{
}
}
},
"requests.Save": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"requests.SaveConfig": {
"type": "object",
"properties": {

View File

@@ -1251,7 +1251,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.Exist"
"$ref": "#/definitions/requests.Save"
}
},
{
@@ -1366,21 +1366,19 @@
],
"summary": "上传文件",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.NotExist"
}
},
{
"type": "file",
"description": "file",
"name": "file",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "path",
"name": "path",
"in": "formData",
"required": true
}
],
"responses": {
@@ -3064,6 +3062,17 @@
}
}
},
"requests.Save": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"requests.SaveConfig": {
"type": "object",
"properties": {

View File

@@ -380,6 +380,13 @@ definitions:
id:
type: integer
type: object
requests.Save:
properties:
content:
type: string
path:
type: string
type: object
requests.SaveConfig:
properties:
domains:
@@ -1230,7 +1237,7 @@ paths:
name: data
required: true
schema:
$ref: '#/definitions/requests.Exist'
$ref: '#/definitions/requests.Save'
- description: content
in: body
name: content
@@ -1301,17 +1308,16 @@ paths:
- application/json
description: 上传文件到给定路径
parameters:
- description: request
in: body
name: data
required: true
schema:
$ref: '#/definitions/requests.NotExist'
- description: file
in: formData
name: file
required: true
type: file
- description: path
in: formData
name: path
required: true
type: string
produces:
- application/json
responses:

View File

@@ -129,7 +129,7 @@ func Api() {
r.Post("archive", fileController.Archive)
r.Post("unArchive", fileController.UnArchive)
r.Post("search", fileController.Search)
r.Post("list", fileController.List)
r.Get("list", fileController.List)
})
r.Prefix("monitor").Middleware(middleware.Jwt()).Group(func(r route.Router) {
monitorController := controllers.NewMonitorController()