repo_description: A container platform that needs no Kubernetes learning, Build, deploy, assemble, and manage apps on Kubernetes, no K8s expertise needed, all in a graphical platform.
repo_topics: application cloud-native cloud-native-applications continuous-delivery devops heroku hybrid-cloud k8s kubernetes microservice-architecture microservice-governance multi-cloud multi-cluster paas rainbond saas service-mesh
code: package controller
  
  import (
  	"archive/tar"
  	"compress/gzip"
  	"encoding/json"
  	"errors"
  	"github.com/go-chi/chi"
  	"github.com/goodrain/rainbond/db"
  	httputil "github.com/goodrain/rainbond/util/http"
  	"io"
  	"math/rand"
  	"net/http"
  	"os"
  	"path"
  	"path/filepath"
  	"reflect"
  	"strings"
  )
  
  // LongVersionStruct -
  type LongVersionStruct struct{}
  
  // BaseUploadPath lang version base dir
  const BaseUploadPath = "/grdata/lang"
  const LSUploadPath = "/run/lang"
  
  // 定义允许上传的文件扩展名白名单
  var allowedExtensions = map[string]bool{
  	".jar":    true,
  	".tar.gz": true,
  }
  
  func (t *LongVersionStruct) UploadLongVersion(w http.ResponseWriter, r *http.Request) {
  	// 从表单中读取文件
  	file, fileHeader, err := r.FormFile("file")
  	if err != nil {
  		sendResponse(w, http.StatusBadRequest, "failure", "get file failure.")
  		return
  	}
  	// defer 结束时关闭文件
  	defer file.Close()
  
  	// 获取文件扩展名并转换为小写
  	fileName := fileHeader.Filename
  	fileExtension := strings.ToLower(filepath.Ext(fileName))
  
  	// 验证文件扩展名是否在白名单中
  	if !allowedExtensions[fileExtension] && !(strings.HasSuffix(fileName, ".tar.gz")) {
  		sendResponse(w, http.StatusBadRequest, "failure", "file type not allowed. Only .jar and .tar.gz files are permitted.")
  		return
  	}
  
  	// 验证文件内容格式
  	if fileExtension == ".tar.gz" || strings.HasSuffix(fileName, ".tar.gz") {
  		if err := validateTarGz(file); err != nil {
  			sendResponse(w, http.StatusBadRequest, "failure", "invalid tar.gz file: "+err.Error())
  			return
  		}
  	} else if fileExtension == ".jar" {
  		if err := validateJar(file); err != nil {
  			sendResponse(w, http.StatusBadRequest, "failure", "invalid jar file: "+err.Error())
  			return
  		}
  	}
  
  	// 生成事件ID
  	eventID, err := generateRandomString(32)
  	if err != nil {
  		sendResponse(w, http.StatusInternalServerError, "failure", "failed to generate event ID: "+err.Error())
  		return
  	}
  
  	langPath := path.Join(LSUploadPath, eventID)
  	if _, err = os.Stat(langPath); os.IsNotExist(err) {
  		// 创建文件夹
  		err := os.MkdirAll(langPath, os.ModePerm)
  		if err != nil {
  			sendResponse(w, http.StatusInternalServerError, "failure", "dir does not exist, mkdir failure")
  			return
  		}
  	}
  
  	// 创建文件
  	newFile, err := os.Create(path.Join(langPath, fileHeader.Filename))
  	if err != nil {
  		sendResponse(w, http.StatusInternalServerError, "failure", "Create file error: "+err.Error())
  		return
  	}
  	// defer 结束时关闭文件
  	defer newFile.Close()
  
  	// 将文件写到本地
  	if _, err = file.Seek(0, io.SeekStart); err != nil {
  		sendResponse(w, http.StatusInternalServerError, "failure", "failed to reset file pointer: "+err.Error())
  		return
  	}
  	_, err = io.Copy(newFile, file)
  	if err != nil {
  		sendResponse(w, http.StatusInternalServerError, "failure", "Failed to write file: "+err.Error())
  		return
  	}
  
  	long := struct {
  		EventID  string `json:"event_id"`
  		FileName string `json:"file_name"`
  	}{
  		eventID,
  		fileHeader.Filename,
  	}
  
  	origin := r.Header.Get("Origin")
  	w.Header().Add("Access-Control-Allow-Origin", origin)
  	w.Header().Add("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT, OPTIONS")
  	w.Header().Add("Access-Control-Allow-Credentials", "true")
  	w.Header().Add("Access-Control-Allow-Headers", "authorization,x_region_name,x_team_name,x-requested-with")
  	sendResponse(w, http.StatusOK, "successful", long)
  }
  
  // validateTarGz 验证上传的文件是否为有效的 tar.gz 格式
  func validateTarGz(file io.Reader) error {
  	gzr, err := gzip.NewReader(file)
  	if err != nil {
  		return err
  	}
  	defer gzr.Close()
  
  	tarReader := tar.NewReader(gzr)
  	_, err = tarReader.Next() // 尝试读取第一个文件头，以验证内容
  	if err != nil {
  		return err
  	}
  	return nil
  }
  
  // validateJar 验证上传的文件是否为有效的 jar 格式
  func validateJar(file io.Reader) error {
  	// JAR 文件实际上是 ZIP 格式，因此可以通过尝试读取 ZIP 头来进行简单验证
  	buffer := make([]byte, 4)
  	if _, err := file.Read(buffer); err != nil {
  		return err
  	}
  	if string(buffer) != "PK\x03\x04" { // ZIP 文件的魔术字节
  		return errors.New("invalid jar file header")
  	}
  	return nil
  }
  
  // DownloadLongVersion -
  func (t *LongVersionStruct) DownloadLongVersion(w http.ResponseWriter, r *http.Request) {
  	language := strings.TrimSpace(chi.URLParam(r, "language"))
  	version := strings.TrimSpace(chi.URLParam(r, "version"))
  	ver, err := db.GetManager().LongVersionDao().GetVersionByLanguageAndVersion(language, version)
  	if err != nil {
  		sendResponse(w, http.StatusBadRequest, "failure", "get version failure"+err.Error())
  		return
  	}
  	http.ServeFile(w, r, path.Join(BaseUploadPath, ver.EventID, ver.FileName))
  }
  
  // OptionLongVersion -
  func (t *LongVersionStruct) OptionLongVersion(w http.ResponseWriter, r *http.Request) {
  	origin := r.Header.Get("Origin")
  	w.Header().Add("Access-Control-Allow-Origin", origin)
  	w.Header().Add("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT, OPTIONS")
  	w.Header().Add("Access-Control-Allow-Credentials", "true")
  	w.Header().Add("Access-Control-Allow-Headers", "authorization,x_region_name,x_team_name,content-type,x-requested-with")
  	httputil.ReturnSuccess(r, w, nil)
  	return
  }
  
  func generateRandomString(n int) (string, error) {
  	const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
  	bytes := make([]byte, n)
  	if _, err := rand.Read(bytes); err != nil {
  		return "", err
  	}
  	for i, b := range bytes {
  		bytes[i] = letters[b%byte(len(letters))]
  	}
  	return string(bytes), nil
  }
  
  // APIResponse =
  type APIResponse struct {
  	Code    int         `json:"code"`
  	Message string      `json:"message"`
  	Bean    interface{} `json:"bean,omitempty"`
  	List    interface{} `json:"list,omitempty"`
  }
  
  func sendResponse(w http.ResponseWriter, code int, message string, data interface{}) {
  	w.Header().Set("Content-Type", "application/json")
  	w.WriteHeader(code)
  
  	response := APIResponse{
  		Code:    code,
  		Message: message,
  	}
  
  	switch reflect.TypeOf(data).Kind() {
  	case reflect.Slice, reflect.Array:
  		response.List = data
  	default:
  		response.Bean = data
  	}
  
  	json.NewEncoder(w).Encode(response)
  }
  