admin_backend/controller/apk.go
2026-05-07 14:39:24 +08:00

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()
}