230 lines
5.2 KiB
Go
230 lines
5.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"backend/util"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const (
|
|
apkStorageRoot = "./runtime/apk"
|
|
apkManifestName = "manifest.json"
|
|
defaultApkUploadToken = "apk-upload-token"
|
|
apkUploadTokenHeader = "X-Apk-Upload-Token"
|
|
apkUploadTokenEnvName = "APK_UPLOAD_TOKEN"
|
|
uploadedAtTimeFormat = time.RFC3339
|
|
defaultApkStorageName = "current.apk"
|
|
)
|
|
|
|
var apkEnvironments = []string{"dev", "stable", "prod"}
|
|
|
|
type apkPackageMeta struct {
|
|
DownloadPath string `json:"downloadPath"`
|
|
Env string `json:"env"`
|
|
Exists bool `json:"exists"`
|
|
FileName string `json:"fileName"`
|
|
Size int64 `json:"size"`
|
|
UploadedAt string `json:"uploadedAt"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
type apkManifest struct {
|
|
Packages map[string]apkPackageMeta `json:"packages"`
|
|
}
|
|
|
|
func UploadApkPackage(c *gin.Context) {
|
|
if !validateApkUploadToken(c.GetHeader(apkUploadTokenHeader)) {
|
|
failed(c, "上传令牌无效")
|
|
return
|
|
}
|
|
|
|
env := strings.TrimSpace(c.PostForm("env"))
|
|
if !isValidApkEnv(env) {
|
|
failed(c, "无效的 apk 环境,必须是 dev、stable 或 prod")
|
|
return
|
|
}
|
|
|
|
fileHeader, err := c.FormFile("file")
|
|
if err != nil {
|
|
failed(c, "获取 apk 文件失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
if strings.ToLower(filepath.Ext(fileHeader.Filename)) != ".apk" {
|
|
failed(c, "仅支持上传 .apk 文件")
|
|
return
|
|
}
|
|
|
|
if err := os.MkdirAll(apkEnvDir(env), 0755); err != nil {
|
|
failed(c, "创建 apk 目录失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
targetPath := apkFilePath(env)
|
|
if err := c.SaveUploadedFile(fileHeader, targetPath); err != nil {
|
|
failed(c, "保存 apk 文件失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
version := strings.TrimSpace(c.PostForm("version"))
|
|
if version == "" {
|
|
version = strings.TrimSuffix(fileHeader.Filename, filepath.Ext(fileHeader.Filename))
|
|
}
|
|
|
|
meta := apkPackageMeta{
|
|
DownloadPath: apkDownloadPath(env),
|
|
Env: env,
|
|
Exists: true,
|
|
FileName: fileHeader.Filename,
|
|
Size: fileHeader.Size,
|
|
UploadedAt: time.Now().Format(uploadedAtTimeFormat),
|
|
Version: version,
|
|
}
|
|
|
|
manifest, err := loadApkManifest()
|
|
if err != nil {
|
|
failed(c, "读取 apk 清单失败: "+err.Error())
|
|
return
|
|
}
|
|
manifest.Packages[env] = meta
|
|
|
|
if err := saveApkManifest(manifest); err != nil {
|
|
failed(c, "保存 apk 清单失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("apk uploaded env=%s file=%s size=%d", env, fileHeader.Filename, fileHeader.Size)
|
|
success(c, meta)
|
|
}
|
|
|
|
func GetApkPackages(c *gin.Context) {
|
|
manifest, err := loadApkManifest()
|
|
if err != nil {
|
|
failed(c, "读取 apk 清单失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
packages := make([]apkPackageMeta, 0, len(apkEnvironments))
|
|
for _, env := range apkEnvironments {
|
|
meta := manifest.Packages[env]
|
|
if meta.Env == "" {
|
|
meta = apkPackageMeta{Env: env}
|
|
}
|
|
meta.Exists = fileExists(apkFilePath(env))
|
|
meta.DownloadPath = apkDownloadPath(env)
|
|
packages = append(packages, meta)
|
|
}
|
|
|
|
success(c, packages)
|
|
}
|
|
|
|
func DownloadApkPackage(c *gin.Context) {
|
|
env := c.Param("env")
|
|
if !isValidApkEnv(env) {
|
|
failed(c, "无效的 apk 环境")
|
|
return
|
|
}
|
|
|
|
manifest, err := loadApkManifest()
|
|
if err != nil {
|
|
failed(c, "读取 apk 清单失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
targetPath := apkFilePath(env)
|
|
if !fileExists(targetPath) {
|
|
failed(c, fmt.Sprintf("%s 环境暂无可下载的 apk 包", env))
|
|
return
|
|
}
|
|
|
|
meta := manifest.Packages[env]
|
|
downloadName := meta.FileName
|
|
if strings.TrimSpace(downloadName) == "" {
|
|
downloadName = fmt.Sprintf("%s.apk", env)
|
|
}
|
|
|
|
util.AddAdminLog(c, "下载客户端APK", gin.H{"env": env, "file": downloadName})
|
|
c.FileAttachment(targetPath, downloadName)
|
|
}
|
|
|
|
func validateApkUploadToken(token string) bool {
|
|
expectedToken := strings.TrimSpace(os.Getenv(apkUploadTokenEnvName))
|
|
if expectedToken == "" {
|
|
expectedToken = defaultApkUploadToken
|
|
}
|
|
return strings.TrimSpace(token) != "" && token == expectedToken
|
|
}
|
|
|
|
func apkEnvDir(env string) string {
|
|
return filepath.Join(apkStorageRoot, env)
|
|
}
|
|
|
|
func apkFilePath(env string) string {
|
|
return filepath.Join(apkEnvDir(env), defaultApkStorageName)
|
|
}
|
|
|
|
func apkManifestPath() string {
|
|
return filepath.Join(apkStorageRoot, apkManifestName)
|
|
}
|
|
|
|
func apkDownloadPath(env string) string {
|
|
return fmt.Sprintf("/api/apk/download/%s", env)
|
|
}
|
|
|
|
func isValidApkEnv(env string) bool {
|
|
for _, item := range apkEnvironments {
|
|
if item == env {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func loadApkManifest() (*apkManifest, error) {
|
|
manifest := &apkManifest{Packages: map[string]apkPackageMeta{}}
|
|
if err := os.MkdirAll(apkStorageRoot, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := os.ReadFile(apkManifestPath())
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return manifest, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return manifest, nil
|
|
}
|
|
|
|
if err := json.Unmarshal(data, manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
if manifest.Packages == nil {
|
|
manifest.Packages = map[string]apkPackageMeta{}
|
|
}
|
|
return manifest, nil
|
|
}
|
|
|
|
func saveApkManifest(manifest *apkManifest) error {
|
|
data, err := json.MarshalIndent(manifest, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(apkManifestPath(), data, 0644)
|
|
}
|
|
|
|
func fileExists(path string) bool {
|
|
info, err := os.Stat(path)
|
|
return err == nil && !info.IsDir()
|
|
}
|