diff --git a/.codex_tat_action.py b/.codex_tat_action.py new file mode 100644 index 00000000..e6489450 --- /dev/null +++ b/.codex_tat_action.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import csv +import os +import sys + +sys.path.insert(0, "/Users/hy/Documents/hy/deploy-platform") + +from deploy.tencent_tat.hyapp_tat_deploy import build_tat_client, run_tat_shell + + +with open( + "/Users/hy/Documents/环境/haiyi腾讯云临时服务器秘钥.csv", + encoding="utf-8-sig", + newline="", +) as handle: + credential = next(csv.DictReader(handle)) + +os.environ["TENCENTCLOUD_SECRET_ID"] = credential["SecretId"].strip() +os.environ["TENCENTCLOUD_SECRET_KEY"] = credential["SecretKey"].strip() +os.environ["TENCENTCLOUD_REGION"] = "me-saudi-arabia" + +client = build_tat_client({"region": "me-saudi-arabia"}) +script = r'''set -euo pipefail +hostname +docker system df +du -sh /opt/deploy/sources/hyapp-server /var/lib/docker 2>/dev/null || true +docker images --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.Size}} {{.CreatedSince}}' | grep 'hyapp/admin-server' | head -n 10 || true +''' + +result = run_tat_shell( + client, + instance_id="ins-ntmpcd3a", + script=script, + command_name="codex-admin-server-disk-precheck", + timeout_seconds=60, + poll_seconds=2, +) +print(result.get("status")) +print(result.get("output", "")) diff --git a/server/admin/internal/modules/resource/handler.go b/server/admin/internal/modules/resource/handler.go index c4f119ce..03f09db3 100644 --- a/server/admin/internal/modules/resource/handler.go +++ b/server/admin/internal/modules/resource/handler.go @@ -484,6 +484,28 @@ func (h *Handler) UpdateResourceGroup(c *gin.Context) { response.OK(c, group) } +func (h *Handler) DeleteResourceGroup(c *gin.Context) { + groupID, ok := parseID(c, "group_id") + if !ok { + return + } + // 删除资源组只关闭后续查询与发放入口,历史发放记录和已固化快照仍需保留用于审计与补偿。 + resp, err := h.wallet.SetResourceGroupStatus(c.Request.Context(), &walletv1.SetResourceGroupStatusRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + GroupId: groupID, + Status: "deleted", + OperatorUserId: actorID(c), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + group := resourceGroupFromProto(resp.GetGroup()) + h.auditLog(c, "delete-resource-group", "resource_groups", fmt.Sprintf("%d", group.GroupID), "success", group.GroupCode) + response.OK(c, group) +} + func (h *Handler) EnableResourceGroup(c *gin.Context) { h.setResourceGroupStatus(c, "active") } diff --git a/server/admin/internal/modules/resource/routes.go b/server/admin/internal/modules/resource/routes.go index 99608009..5cc11d67 100644 --- a/server/admin/internal/modules/resource/routes.go +++ b/server/admin/internal/modules/resource/routes.go @@ -31,6 +31,7 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.PUT("/admin/resource-groups/identity-auto-grant-config", middleware.RequirePermission("resource-group:update"), h.UpdateResourceIdentityAutoGrantConfig) protected.GET("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:view"), h.GetResourceGroup) protected.PUT("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:update"), h.UpdateResourceGroup) + protected.DELETE("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:delete"), h.DeleteResourceGroup) protected.PUT("/admin/resource-groups/:group_id/items", middleware.RequirePermission("resource-group:update"), h.UpdateResourceGroup) protected.POST("/admin/resource-groups/:group_id/enable", middleware.RequirePermission("resource-group:update"), h.EnableResourceGroup) protected.POST("/admin/resource-groups/:group_id/disable", middleware.RequirePermission("resource-group:update"), h.DisableResourceGroup) diff --git a/server/admin/internal/repository/role_permission_matrix.go b/server/admin/internal/repository/role_permission_matrix.go index 9175b682..e314f46b 100644 --- a/server/admin/internal/repository/role_permission_matrix.go +++ b/server/admin/internal/repository/role_permission_matrix.go @@ -96,7 +96,7 @@ var ( } resourceManage = []string{ "resource:create", "resource:update", "resource:delete", "upload:create", - "resource-shop:update", "resource-group:create", "resource-group:update", + "resource-shop:update", "resource-group:create", "resource-group:update", "resource-group:delete", "gift:create", "gift:update", "gift:status", "gift:delete", "resource-grant:create", "resource-grant:revoke", "emoji-pack:create", } diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index 498a1096..aa40c9b7 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -69,6 +69,7 @@ var defaultPermissions = []model.Permission{ {Name: "资源组查看", Code: "resource-group:view", Kind: "menu"}, {Name: "资源组创建", Code: "resource-group:create", Kind: "button"}, {Name: "资源组更新", Code: "resource-group:update", Kind: "button"}, + {Name: "资源组删除", Code: "resource-group:delete", Kind: "button"}, {Name: "资源赠送查看", Code: "resource-grant:view", Kind: "menu"}, {Name: "资源赠送创建", Code: "resource-grant:create", Kind: "button"}, {Name: "资源赠送撤销", Code: "resource-grant:revoke", Kind: "button"}, diff --git a/server/admin/migrations/093_role_permission_matrix.sql b/server/admin/migrations/093_role_permission_matrix.sql index 276f0e5d..a20aca85 100644 --- a/server/admin/migrations/093_role_permission_matrix.sql +++ b/server/admin/migrations/093_role_permission_matrix.sql @@ -112,7 +112,7 @@ WHERE role.code = 'ops-admin' 'room-robot:view', 'room-robot:create', 'room-robot:update', 'app-config:view', 'app-config:update', 'full-server-notice:send', 'resource:view', 'resource:create', 'resource:update', 'resource:delete', 'upload:create', - 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', + 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', 'resource-group:delete', 'gift:view', 'gift:create', 'gift:update', 'gift:status', 'gift:delete', 'resource-grant:view', 'resource-grant:create', 'resource-grant:revoke', 'emoji-pack:view', 'emoji-pack:create', 'coin-ledger:view', 'coin-seller-ledger:view', 'coin-adjustment:view', 'coin-adjustment:create', 'report:view', @@ -159,7 +159,7 @@ WHERE role.code = 'operations-specialist' 'room-robot:view', 'room-robot:create', 'room-robot:update', 'app-config:view', 'app-config:update', 'full-server-notice:send', 'resource:view', 'resource:create', 'resource:update', 'resource:delete', 'upload:create', - 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', + 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', 'resource-group:delete', 'gift:view', 'gift:create', 'gift:update', 'gift:status', 'gift:delete', 'resource-grant:view', 'resource-grant:create', 'resource-grant:revoke', 'emoji-pack:view', 'emoji-pack:create', 'coin-ledger:view', 'coin-seller-ledger:view', 'report:view', @@ -191,7 +191,7 @@ WHERE role.code = 'product-lead' 'app-config:view', 'app-config:update', 'app-version:view', 'app-version:create', 'app-version:update', 'app-version:delete', 'full-server-notice:view', 'full-server-notice:send', 'resource:view', 'resource:create', 'resource:update', 'resource:delete', 'upload:create', - 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', + 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', 'resource-group:delete', 'gift:view', 'gift:create', 'gift:update', 'gift:status', 'gift:delete', 'resource-grant:view', 'resource-grant:create', 'resource-grant:revoke', 'emoji-pack:view', 'emoji-pack:create', 'coin-ledger:view', 'coin-seller-ledger:view', 'report:view', 'gift-diamond:view', 'gift-diamond:update', @@ -224,7 +224,7 @@ WHERE role.code = 'product-specialist' 'room:view', 'room-pin:view', 'room-config:view', 'room-whitelist:view', 'room-robot:view', 'room-robot:create', 'room-robot:update', 'app-config:view', 'app-version:view', 'full-server-notice:view', 'resource:view', 'resource:create', 'resource:update', 'resource:delete', 'upload:create', - 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', + 'resource-shop:view', 'resource-shop:update', 'resource-group:view', 'resource-group:create', 'resource-group:update', 'resource-group:delete', 'gift:view', 'gift:create', 'gift:update', 'gift:status', 'gift:delete', 'resource-grant:view', 'resource-grant:create', 'resource-grant:revoke', 'emoji-pack:view', 'emoji-pack:create', 'coin-ledger:view', 'coin-seller-ledger:view', 'report:view', diff --git a/server/admin/migrations/120_resource_group_delete_permission.sql b/server/admin/migrations/120_resource_group_delete_permission.sql new file mode 100644 index 00000000..5c8df196 --- /dev/null +++ b/server/admin/migrations/120_resource_group_delete_permission.sql @@ -0,0 +1,18 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES + ('资源组删除', 'resource-group:delete', 'button', '', @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE + name = VALUES(name), + kind = VALUES(kind), + description = VALUES(description), + updated_at_ms = @now_ms; + +INSERT IGNORE INTO admin_role_permissions (role_id, permission_id) +SELECT admin_role.id, admin_permission.id +FROM admin_roles admin_role +JOIN admin_permissions admin_permission +WHERE admin_role.code IN ('platform-admin', 'ops-admin', 'operations-specialist', 'product-lead', 'product-specialist') + AND admin_permission.code = 'resource-group:delete'; diff --git a/services/wallet-service/internal/storage/mysql/resource_group_repository.go b/services/wallet-service/internal/storage/mysql/resource_group_repository.go index eb6d551a..6afce06a 100644 --- a/services/wallet-service/internal/storage/mysql/resource_group_repository.go +++ b/services/wallet-service/internal/storage/mysql/resource_group_repository.go @@ -183,7 +183,8 @@ func (r *Repository) SetResourceGroupStatus(ctx context.Context, command resourc } ctx = contextWithCommandApp(ctx, command.AppCode) command.Status = resourcedomain.NormalizeStatus(command.Status) - if !resourcedomain.ValidStatus(command.Status) { + // deleted 仅供受独立删除权限保护的管理端入口使用;创建、编辑仍只接受 active/disabled。 + if !resourcedomain.ValidStatus(command.Status) && command.Status != resourcedomain.StatusDeleted { return resourcedomain.ResourceGroup{}, xerr.New(xerr.InvalidArgument, "status is invalid") } tx, err := r.db.BeginTx(ctx, nil) @@ -468,7 +469,8 @@ func scanResourceGroupItem(scanner scanTarget) (resourcedomain.ResourceGroupItem } func resourceGroupWhereSQL(query resourcedomain.ListResourceGroupsQuery) (string, []any) { - where := `WHERE app_code = ?` + // 软删除行保留给历史发放、快照和审计链路,任何资源组列表都不能重新暴露这些行。 + where := `WHERE app_code = ? AND status <> 'deleted'` args := []any{query.AppCode} if query.ActiveOnly { where += ` AND status = 'active'`