|
// ClashApp - 簡單的 Golang 應用程式 |
|
package main |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"log" |
|
"net/http" |
|
"os" |
|
"time" |
|
|
|
"github.com/minio/minio-go/v7" |
|
"github.com/minio/minio-go/v7/pkg/credentials" |
|
) |
|
|
|
var ( |
|
endpoint string = os.Getenv("MINIO_ENDPOINT") |
|
accessKeyID string = os.Getenv("MINIO_ACCESS_KEY") |
|
secretAccessKey string = os.Getenv("MINIO_SECRET_KEY") |
|
bucketName string = os.Getenv("MINIO_BUCKET_NAME") |
|
|
|
minioClient *minio.Client |
|
) |
|
|
|
func main() { |
|
log.Println("正在啟動 ClashApp...") |
|
|
|
if endpoint == "" || accessKeyID == "" || secretAccessKey == "" || bucketName == "" { |
|
log.Fatalf("錯誤: MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, BUCKET_NAME 環境變數必須設定!") |
|
} |
|
|
|
// 初始化 MinIO 客戶端 (如果配置錯誤,這裡可能會 panic 或後續操作失敗) |
|
log.Printf("嘗試連接 MinIO: %s\n", endpoint) |
|
var err error |
|
minioClient, err = minio.New(endpoint, &minio.Options{ |
|
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), |
|
}) |
|
if err != nil { |
|
log.Fatalf("初始化 MinIO 客戶端失敗: %v", err) |
|
} |
|
|
|
log.Printf("成功初始化 MinIO 客戶端") |
|
|
|
log.Println("檢查 MinIO Bucket...") |
|
ok, err := minioClient.BucketExists(context.Background(), bucketName) |
|
if err != nil { |
|
log.Fatalf("檢查 Bucket '%s' 失敗: %v", bucketName, err) |
|
} |
|
if !ok { |
|
log.Fatalf("Bucket '%s' 不存在!", bucketName) |
|
} |
|
log.Printf("Bucket '%s' 存在。\n", bucketName) |
|
|
|
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { |
|
// 一個非常基礎的健康檢查,實際應用應更完善 |
|
fmt.Fprintf(w, "ok") |
|
}) |
|
|
|
http.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) { |
|
// 模擬準備就緒探針,應檢查 MinIO 連接等 |
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) |
|
defer cancel() |
|
_, err := minioClient.BucketExists(ctx, bucketName) |
|
if err != nil { |
|
http.Error(w, "MinIO not ready", http.StatusServiceUnavailable) |
|
return |
|
} |
|
fmt.Fprintf(w, "ready") |
|
}) |
|
|
|
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { |
|
if r.Method != http.MethodPost { |
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
|
return |
|
} |
|
|
|
// 限制最大上傳大小 (例如 10MB) |
|
r.Body = http.MaxBytesReader(w, r.Body, 10<<20) |
|
err := r.ParseMultipartForm(10 << 20) |
|
if err != nil { |
|
http.Error(w, "檔案太大或格式錯誤", http.StatusBadRequest) |
|
return |
|
} |
|
|
|
file, handler, err := r.FormFile("file") |
|
if err != nil { |
|
http.Error(w, "無法取得上傳檔案", http.StatusBadRequest) |
|
return |
|
} |
|
defer file.Close() |
|
|
|
objectName := handler.Filename |
|
contentType := handler.Header.Get("Content-Type") |
|
if contentType == "" { |
|
contentType = "application/octet-stream" |
|
} |
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
|
defer cancel() |
|
|
|
info, err := minioClient.PutObject(ctx, bucketName, objectName, file, handler.Size, minio.PutObjectOptions{ |
|
ContentType: contentType, |
|
}) |
|
if err != nil { |
|
log.Printf("上傳到 MinIO 失敗: %v", err) |
|
http.Error(w, "上傳失敗", http.StatusInternalServerError) |
|
return |
|
} |
|
|
|
log.Printf("成功上傳: %s (%d bytes)", info.Key, info.Size) |
|
fmt.Fprintf(w, "uploaded: %s\n", info.Key) |
|
}) |
|
|
|
log.Println("ClashApp 正在監聽 :8080") |
|
if err := http.ListenAndServe(":8080", nil); err != nil { |
|
log.Fatalf("無法啟動 HTTP 伺服器: %v", err) |
|
} |
|
} |