diff --git a/controller/apk.go b/controller/apk.go index 2b6b5d6..fc4e769 100644 --- a/controller/apk.go +++ b/controller/apk.go @@ -22,13 +22,16 @@ const ( apkUploadTokenEnvName = "APK_UPLOAD_TOKEN" uploadedAtTimeFormat = time.RFC3339 defaultApkStorageName = "current.apk" + defaultApkPackageType = "without_sdk" ) var apkEnvironments = []string{"dev", "stable", "prod"} +var apkPackageTypes = []string{"with_sdk", "without_sdk"} type apkPackageMeta struct { DownloadPath string `json:"downloadPath"` Env string `json:"env"` + PackageType string `json:"packageType"` Exists bool `json:"exists"` FileName string `json:"fileName"` Size int64 `json:"size"` @@ -36,8 +39,13 @@ type apkPackageMeta struct { Version string `json:"version"` } +type apkEnvPackages struct { + Env string `json:"env"` + Variants map[string]apkPackageMeta `json:"variants"` +} + type apkManifest struct { - Packages map[string]apkPackageMeta `json:"packages"` + Packages map[string]apkEnvPackages `json:"packages"` } func UploadApkPackage(c *gin.Context) { @@ -52,6 +60,12 @@ func UploadApkPackage(c *gin.Context) { return } + packageType, err := parseApkPackageType(c.PostForm("packageType")) + if err != nil { + failed(c, err.Error()) + return + } + fileHeader, err := c.FormFile("file") if err != nil { failed(c, "获取 apk 文件失败: "+err.Error()) @@ -63,12 +77,12 @@ func UploadApkPackage(c *gin.Context) { return } - if err := os.MkdirAll(apkEnvDir(env), 0755); err != nil { + if err := os.MkdirAll(apkPackageDir(env, packageType), 0755); err != nil { failed(c, "创建 apk 目录失败: "+err.Error()) return } - targetPath := apkFilePath(env) + targetPath := apkFilePath(env, packageType) if err := c.SaveUploadedFile(fileHeader, targetPath); err != nil { failed(c, "保存 apk 文件失败: "+err.Error()) return @@ -80,8 +94,9 @@ func UploadApkPackage(c *gin.Context) { } meta := apkPackageMeta{ - DownloadPath: apkDownloadPath(env), + DownloadPath: apkDownloadPath(env, packageType), Env: env, + PackageType: packageType, Exists: true, FileName: fileHeader.Filename, Size: fileHeader.Size, @@ -94,14 +109,16 @@ func UploadApkPackage(c *gin.Context) { failed(c, "读取 apk 清单失败: "+err.Error()) return } - manifest.Packages[env] = meta + envPackages := ensureEnvPackages(manifest, env) + envPackages.Variants[packageType] = meta + manifest.Packages[env] = envPackages 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) + log.Printf("apk uploaded env=%s packageType=%s file=%s size=%d", env, packageType, fileHeader.Filename, fileHeader.Size) success(c, meta) } @@ -112,15 +129,20 @@ func GetApkPackages(c *gin.Context) { return } - packages := make([]apkPackageMeta, 0, len(apkEnvironments)) + packages := make([]apkEnvPackages, 0, len(apkEnvironments)) for _, env := range apkEnvironments { - meta := manifest.Packages[env] - if meta.Env == "" { - meta = apkPackageMeta{Env: env} + envPackages := ensureEnvPackages(manifest, env) + for _, packageType := range apkPackageTypes { + meta := envPackages.Variants[packageType] + if meta.Env == "" { + meta = apkPackageMeta{Env: env, PackageType: packageType} + } + meta.Exists = fileExists(apkFilePath(env, packageType)) + meta.DownloadPath = apkDownloadPath(env, packageType) + meta.PackageType = packageType + envPackages.Variants[packageType] = meta } - meta.Exists = fileExists(apkFilePath(env)) - meta.DownloadPath = apkDownloadPath(env) - packages = append(packages, meta) + packages = append(packages, envPackages) } success(c, packages) @@ -132,6 +154,11 @@ func DownloadApkPackage(c *gin.Context) { failed(c, "无效的 apk 环境") return } + packageType, err := parseApkPackageType(c.Query("packageType")) + if err != nil { + failed(c, err.Error()) + return + } manifest, err := loadApkManifest() if err != nil { @@ -139,19 +166,20 @@ func DownloadApkPackage(c *gin.Context) { return } - targetPath := apkFilePath(env) + targetPath := apkFilePath(env, packageType) if !fileExists(targetPath) { - failed(c, fmt.Sprintf("%s 环境暂无可下载的 apk 包", env)) + failed(c, fmt.Sprintf("%s 环境暂无可下载的 %s apk 包", env, packageType)) return } - meta := manifest.Packages[env] + envPackages := ensureEnvPackages(manifest, env) + meta := envPackages.Variants[packageType] downloadName := meta.FileName if strings.TrimSpace(downloadName) == "" { - downloadName = fmt.Sprintf("%s.apk", env) + downloadName = fmt.Sprintf("%s-%s.apk", env, packageType) } - util.AddAdminLog(c, "下载客户端APK", gin.H{"env": env, "file": downloadName}) + util.AddAdminLog(c, "下载客户端APK", gin.H{"env": env, "packageType": packageType, "file": downloadName}) c.FileAttachment(targetPath, downloadName) } @@ -167,16 +195,20 @@ func apkEnvDir(env string) string { return filepath.Join(apkStorageRoot, env) } -func apkFilePath(env string) string { - return filepath.Join(apkEnvDir(env), defaultApkStorageName) +func apkPackageDir(env string, packageType string) string { + return filepath.Join(apkEnvDir(env), packageType) +} + +func apkFilePath(env string, packageType string) string { + return filepath.Join(apkPackageDir(env, packageType), defaultApkStorageName) } func apkManifestPath() string { return filepath.Join(apkStorageRoot, apkManifestName) } -func apkDownloadPath(env string) string { - return fmt.Sprintf("/api/apk/download/%s", env) +func apkDownloadPath(env string, packageType string) string { + return fmt.Sprintf("/api/apk/download/%s?packageType=%s", env, packageType) } func isValidApkEnv(env string) bool { @@ -188,8 +220,50 @@ func isValidApkEnv(env string) bool { return false } +func parseApkPackageType(rawType string) (string, error) { + value := strings.TrimSpace(strings.ToLower(rawType)) + if value == "" { + return defaultApkPackageType, nil + } + + aliases := map[string]string{ + "sdk": "with_sdk", + "with-sdk": "with_sdk", + "with_sdk": "with_sdk", + "withsdk": "with_sdk", + "nosdk": "without_sdk", + "no-sdk": "without_sdk", + "no_sdk": "without_sdk", + "without-sdk": "without_sdk", + "without_sdk": "without_sdk", + "withoutsdk": "without_sdk", + } + if alias, ok := aliases[value]; ok { + return alias, nil + } + + for _, item := range apkPackageTypes { + if item == value { + return value, nil + } + } + + return "", fmt.Errorf("无效的 apk 包类型,必须是 with_sdk 或 without_sdk") +} + +func ensureEnvPackages(manifest *apkManifest, env string) apkEnvPackages { + envPackages, ok := manifest.Packages[env] + if !ok || envPackages.Env == "" { + envPackages = apkEnvPackages{Env: env, Variants: map[string]apkPackageMeta{}} + } + if envPackages.Variants == nil { + envPackages.Variants = map[string]apkPackageMeta{} + } + return envPackages +} + func loadApkManifest() (*apkManifest, error) { - manifest := &apkManifest{Packages: map[string]apkPackageMeta{}} + manifest := &apkManifest{Packages: map[string]apkEnvPackages{}} if err := os.MkdirAll(apkStorageRoot, 0755); err != nil { return nil, err } @@ -206,11 +280,39 @@ func loadApkManifest() (*apkManifest, error) { return manifest, nil } - if err := json.Unmarshal(data, manifest); err != nil { + var raw struct { + Packages map[string]json.RawMessage `json:"packages"` + } + if err := json.Unmarshal(data, &raw); err != nil { return nil, err } - if manifest.Packages == nil { - manifest.Packages = map[string]apkPackageMeta{} + for env, rawPackage := range raw.Packages { + var envPackages apkEnvPackages + if err := json.Unmarshal(rawPackage, &envPackages); err == nil && len(envPackages.Variants) > 0 { + if envPackages.Env == "" { + envPackages.Env = env + } + for packageType, meta := range envPackages.Variants { + meta.Env = env + meta.PackageType = packageType + envPackages.Variants[packageType] = meta + } + manifest.Packages[env] = envPackages + continue + } + + var legacyMeta apkPackageMeta + if err := json.Unmarshal(rawPackage, &legacyMeta); err != nil { + return nil, err + } + legacyMeta.Env = env + legacyMeta.PackageType = defaultApkPackageType + manifest.Packages[env] = apkEnvPackages{ + Env: env, + Variants: map[string]apkPackageMeta{ + defaultApkPackageType: legacyMeta, + }, + } } return manifest, nil } diff --git a/log/backend.log b/log/backend.log index e69de29..f6b3e0b 100644 --- a/log/backend.log +++ b/log/backend.log @@ -0,0 +1,123 @@ +[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. + +[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. + - using env: export GIN_MODE=release + - using code: gin.SetMode(gin.ReleaseMode) + +[GIN-debug] POST /api/open/apk/upload --> backend/controller.UploadApkPackage (3 handlers) +[GIN-debug] POST /api/auth/login --> backend/controller.Login (3 handlers) +[GIN-debug] GET /api/auth/Codes --> backend/controller.Codes (3 handlers) +[GIN-debug] POST /api/auth/phoneCode --> backend/controller.PhoneCode (3 handlers) +[GIN-debug] POST /api/auth/phoneLogin --> backend/controller.LoginCode (3 handlers) +[GIN-debug] POST /api/feishu/sendInfo --> backend/controller.FeishuSendInfo (3 handlers) +[GIN-debug] POST /api/feishu/sendInfo2 --> backend/controller.FeishuSendInfo2 (3 handlers) +[GIN-debug] POST /api/feishu/sendWeekInfo --> backend/controller.FeishuSendWeekInfo (3 handlers) +[GIN-debug] POST /api/feishu/updateApp --> backend/controller.FeishuUpdateApp (3 handlers) +[GIN-debug] POST /api/feishu/serverInfo --> backend/controller.FeishuServerInfo (3 handlers) +[GIN-debug] POST /api/feishu/notify --> backend/controller.FeishuNotify (3 handlers) +[GIN-debug] POST /api/feishu/notify/client --> backend/controller.FeishuNotifyClient (3 handlers) +[GIN-debug] POST /api/feishu/notify/order --> backend/controller.FeishuNotifyOrder (3 handlers) +[GIN-debug] POST /api/alibaba/zabbix/notify --> backend/controller.AlibabaNotify (3 handlers) +[GIN-debug] POST /api/alibaba/zabbix/recovery --> backend/controller.AlibabaRecovery (3 handlers) +[GIN-debug] POST /api/alibaba/game/notify --> backend/controller.AlibabaGameNotify (3 handlers) +[GIN-debug] POST /api/alibaba/notify/order --> backend/controller.AlibabaNotifyOrder (3 handlers) +[GIN-debug] GET /api/v1/experiments --> backend/controller.ExperimentList (4 handlers) +[GIN-debug] POST /api/v1/experiments --> backend/controller.ExperimentCreate (4 handlers) +[GIN-debug] PUT /api/v1/experiments/:id --> backend/controller.ExperimentUpdate (4 handlers) +[GIN-debug] DELETE /api/v1/experiments/:id --> backend/controller.ExperimentDelete (4 handlers) +[GIN-debug] GET /api/v1/experiments/:id/variants --> backend/controller.ExperimentVariantList (4 handlers) +[GIN-debug] POST /api/v1/experiments/:id/variants --> backend/controller.ExperimentVariantCreate (4 handlers) +[GIN-debug] PUT /api/v1/experiments/:id/variants/:variantId --> backend/controller.ExperimentVariantUpdate (4 handlers) +[GIN-debug] DELETE /api/v1/experiments/:id/variants/:variantId --> backend/controller.ExperimentVariantDelete (4 handlers) +[GIN-debug] GET /api/v1/experiments/:id/whitelist --> backend/controller.ExperimentWhitelistList (4 handlers) +[GIN-debug] POST /api/v1/experiments/:id/whitelist --> backend/controller.ExperimentWhitelistCreate (4 handlers) +[GIN-debug] POST /api/v1/experiments/:id/whitelist/batch --> backend/controller.ExperimentWhitelistBatchCreate (4 handlers) +[GIN-debug] DELETE /api/v1/experiments/:id/whitelist/:userId --> backend/controller.ExperimentWhitelistDelete (4 handlers) +[GIN-debug] GET /api/v1/experiments/:id/results --> backend/controller.ExperimentResult (4 handlers) +[GIN-debug] GET /api/v1/users/:userId/groups --> backend/controller.UserExperimentGroups (4 handlers) +[GIN-debug] GET /api/user/info --> backend/controller.UserInfo (4 handlers) +[GIN-debug] POST /api/statistics/info --> backend/controller.StatisticsInfo (4 handlers) +[GIN-debug] POST /api/statistics/level --> backend/controller.StatisticsLevel (4 handlers) +[GIN-debug] POST /api/statistics/order --> backend/controller.StatisticsOrder (4 handlers) +[GIN-debug] POST /api/statistics/heat --> backend/controller.StatisticsHeat (4 handlers) +[GIN-debug] POST /api/admin/list --> backend/controller.AdminList (4 handlers) +[GIN-debug] POST /api/admin/add --> backend/controller.AdminAdd (4 handlers) +[GIN-debug] POST /api/admin/edit --> backend/controller.AdminEdit (4 handlers) +[GIN-debug] POST /api/admin/delete --> backend/controller.AdminDelete (4 handlers) +[GIN-debug] POST /api/admin/log/list --> backend/controller.AdminLogList (4 handlers) +[GIN-debug] POST /api/admin/config/list --> backend/controller.AdminConfigList (4 handlers) +[GIN-debug] POST /api/admin/config/add --> backend/controller.AdminConfigAdd (4 handlers) +[GIN-debug] POST /api/admin/config/edit --> backend/controller.AdminConfigEdit (4 handlers) +[GIN-debug] POST /api/admin/usergroup/list --> backend/controller.PermissionUserGroupList (4 handlers) +[GIN-debug] POST /api/admin/usergroup/add --> backend/controller.PermissionUserGroupAdd (4 handlers) +[GIN-debug] POST /api/admin/usergroup/edit --> backend/controller.PermissionUserGroupEdit (4 handlers) +[GIN-debug] POST /api/admin/usergroup/delete --> backend/controller.PermissionUserGroupDelete (4 handlers) +[GIN-debug] POST /api/admin/usergroup/role/set --> backend/controller.PermissionGroupRoleSet (4 handlers) +[GIN-debug] POST /api/admin/usergroup/role/list --> backend/controller.PermissionGroupRoleList (4 handlers) +[GIN-debug] POST /api/admin/role/list --> backend/controller.PermissionRoleList (4 handlers) +[GIN-debug] POST /api/admin/role/add --> backend/controller.PermissionRoleAdd (4 handlers) +[GIN-debug] POST /api/admin/role/edit --> backend/controller.PermissionRoleEdit (4 handlers) +[GIN-debug] POST /api/admin/role/delete --> backend/controller.PermissionRoleDelete (4 handlers) +[GIN-debug] POST /api/admin/role/permission/set --> backend/controller.PermissionRolePermissionSet (4 handlers) +[GIN-debug] POST /api/admin/role/permission/list --> backend/controller.PermissionRolePermissionList (4 handlers) +[GIN-debug] POST /api/admin/permission/list --> backend/controller.PermissionPointList (4 handlers) +[GIN-debug] POST /api/admin/permission/add --> backend/controller.PermissionPointAdd (4 handlers) +[GIN-debug] POST /api/admin/permission/edit --> backend/controller.PermissionPointEdit (4 handlers) +[GIN-debug] POST /api/admin/permission/delete --> backend/controller.PermissionPointDelete (4 handlers) +[GIN-debug] POST /api/admin/user/group/list --> backend/controller.PermissionUserGroupRelList (4 handlers) +[GIN-debug] POST /api/admin/user/group/set --> backend/controller.PermissionUserGroupRelSet (4 handlers) +[GIN-debug] POST /api/admin/user/permission/list --> backend/controller.PermissionUserPermissionList (4 handlers) +[GIN-debug] POST /api/admin/user/permission/set --> backend/controller.PermissionUserPermissionSet (4 handlers) +[GIN-debug] POST /api/admin/user/role/list --> backend/controller.PermissionUserRoleList (4 handlers) +[GIN-debug] POST /api/admin/user/role/batch-list --> backend/controller.PermissionUserRoleBatchList (4 handlers) +[GIN-debug] POST /api/log/user --> backend/controller.UserDetail (4 handlers) +[GIN-debug] POST /api/log/asset --> backend/controller.Asset (4 handlers) +[GIN-debug] POST /api/log/event --> backend/controller.Event (4 handlers) +[GIN-debug] POST /api/log/loginCountByMonth --> backend/controller.LoginCountByMonth (4 handlers) +[GIN-debug] POST /api/log/order --> backend/controller.Order (4 handlers) +[GIN-debug] POST /api/user/list --> backend/controller.UserList (4 handlers) +[GIN-debug] POST /api/user/gm --> backend/controller.UserGM (4 handlers) +[GIN-debug] POST /api/user/ban --> backend/controller.UserBan (4 handlers) +[GIN-debug] POST /api/server/list --> backend/controller.AppList (4 handlers) +[GIN-debug] POST /api/server/serverList --> backend/controller.ServerList (4 handlers) +[GIN-debug] POST /api/server/addServer --> backend/controller.AddServer (4 handlers) +[GIN-debug] POST /api/server/nodeList --> backend/controller.NodeList (4 handlers) +[GIN-debug] POST /api/server/addNode --> backend/controller.AddNode (4 handlers) +[GIN-debug] POST /api/server/editServer --> backend/controller.EditServer (4 handlers) +[GIN-debug] POST /api/server/updateApp --> backend/controller.UpdateApp (4 handlers) +[GIN-debug] POST /api/server/updateAppReview --> backend/controller.UpdateAppReview (4 handlers) +[GIN-debug] POST /api/server/updateAppFeishu --> backend/controller.UpdateAppFeishu (4 handlers) +[GIN-debug] POST /api/server/restart --> backend/controller.RestartServer (4 handlers) +[GIN-debug] POST /api/server/reload --> backend/controller.ReloadServer (4 handlers) +[GIN-debug] POST /api/activity/list --> backend/controller.ActivityList (4 handlers) +[GIN-debug] POST /api/activity/edit --> backend/controller.ActivityEdit (4 handlers) +[GIN-debug] POST /api/activity/add --> backend/controller.ActivityAdd (4 handlers) +[GIN-debug] POST /api/activity/delete --> backend/controller.ActivityDelete (4 handlers) +[GIN-debug] POST /api/activity/sync --> backend/controller.ActivitySync (4 handlers) +[GIN-debug] POST /api/mail/send --> backend/controller.SendMail (4 handlers) +[GIN-debug] POST /api/mail/list --> backend/controller.MailList (4 handlers) +[GIN-debug] POST /api/mail/delete --> backend/controller.MailDelete (4 handlers) +[GIN-debug] POST /api/operation/copyUser --> backend/controller.CopyUser (4 handlers) +[GIN-debug] GET /api/apk/packages --> backend/controller.GetApkPackages (4 handlers) +[GIN-debug] GET /api/apk/download/:env --> backend/controller.DownloadApkPackage (4 handlers) +[GIN-debug] POST /api/language/list --> backend/controller.Language (4 handlers) +[GIN-debug] POST /api/language/export --> backend/controller.LanguageExport (4 handlers) +[GIN-debug] POST /api/language/save --> backend/controller.LanguageSave (4 handlers) +[GIN-debug] POST /api/language/add --> backend/controller.LanguageAdd (4 handlers) +[GIN-debug] POST /api/language/delete --> backend/controller.LanguageDelete (4 handlers) +[GIN-debug] GET /api/config/notification --> backend/controller.NotificationConfigGet (4 handlers) +[GIN-debug] PUT /api/config/notification/update --> backend/controller.NotificationConfigSave (4 handlers) +[GIN-debug] POST /api/scripts/copywriting --> backend/controller.Copywriting (4 handlers) +[GIN-debug] POST /api/scripts/copyonline --> backend/controller.CopyOnline (4 handlers) +[GIN-debug] POST /api/scripts/clientImageGitPull --> backend/controller.ClientImageGitPull (4 handlers) +[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. +Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details. +[GIN-debug] Listening and serving HTTP on :5320 +2026/05/07 15:32:59 apk uploaded env=prod packageType=without_sdk file=prod_v1.0.73_20260430_1423_656bd841f_#78.apk size=282603268 +[GIN] 2026/05/07 - 15:32:59 | 200 | 6.29s | ::1 | POST "/api/open/apk/upload" +2026/05/07 15:33:00 monitor server failed, AppId=2, ServerId=0, err=method1 and method2 both failed, method1Err=ServerInfo failed: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 112.124.54.243:0: connectex: The requested address is not valid in its context.", method2Err=failed to send admin message: failed to read from websocket: websocket: close 1006 (abnormal closure): unexpected EOF +2026/05/07 15:33:03 monitor server failed, AppId=0, ServerId=0, err=method1 and method2 both failed, method1Err=dial tcp 101.132.170.198:0: connectex: The requested address is not valid in its context., method2Err=dial tcp 101.132.170.198:0: connectex: The requested address is not valid in its context. +[GIN] 2026/05/07 - 15:33:04 | 200 | 92.07ms | ::1 | GET "/api/apk/packages" +2026/05/07 15:33:05 monitor server failed, AppId=4, ServerId=1, err=method1 and method2 both failed, method1Err=ServerInfo failed: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 1.15.182.107:0: connectex: The requested address is not valid in its context.", method2Err=failed to get websocket: failed to connect to websocket: dial tcp 112.124.54.243:4701: i/o timeout +2026/05/07 15:33:06 monitor server failed, AppId=1, ServerId=3, err=method1 and method2 both failed, method1Err=ServerInfo failed: rpc error: code = DeadlineExceeded desc = received context error while waiting for new LB policy update: context deadline exceeded, method2Err=failed to send admin message: failed to read from websocket: websocket: close 1006 (abnormal closure): unexpected EOF +2026/05/07 15:33:06 monitor server failed, AppId=2, ServerId=3, err=method1 and method2 both failed, method1Err=ServerInfo failed: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 112.124.54.243:50063: connectex: No connection could be made because the target machine actively refused it.", method2Err=dial tcp 112.124.54.243:3653: connectex: No connection could be made because the target machine actively refused it.