From 779d864e80d6e8834398bd483f0baccdcdf31f85 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 8 Jul 2026 18:47:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BB=8F=E7=90=86=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=9B=B4=E6=96=B0=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/internal/modules/hostorg/handler.go | 6 +- .../admin/internal/modules/hostorg/reader.go | 7 +- .../internal/modules/hostorg/reader_test.go | 87 +++++++++++++++++++ .../admin/internal/modules/hostorg/request.go | 18 ++++ 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 server/admin/internal/modules/hostorg/reader_test.go diff --git a/server/admin/internal/modules/hostorg/handler.go b/server/admin/internal/modules/hostorg/handler.go index f4461fae..0076ff02 100644 --- a/server/admin/internal/modules/hostorg/handler.go +++ b/server/admin/internal/modules/hostorg/handler.go @@ -107,8 +107,12 @@ func (h *Handler) UpdateManager(c *gin.Context) { response.BadRequest(c, err.Error()) return } + statusDetail := "" + if req.Status != nil && strings.TrimSpace(*req.Status) != "" { + statusDetail = " status=" + strings.ToLower(strings.TrimSpace(*req.Status)) + } writeHostOrgAuditLog(c, h.audit, "update-manager", "manager_profiles", item.UserID, - fmt.Sprintf("user_id=%d permissions_updated=true", item.UserID)) + fmt.Sprintf("user_id=%d permissions_updated=true%s", item.UserID, statusDetail)) response.OK(c, item) } diff --git a/server/admin/internal/modules/hostorg/reader.go b/server/admin/internal/modules/hostorg/reader.go index 6be000ea..a03e12d4 100644 --- a/server/admin/internal/modules/hostorg/reader.go +++ b/server/admin/internal/modules/hostorg/reader.go @@ -652,8 +652,8 @@ func (r *Reader) CreateManagerProfile(ctx context.Context, userID int64, actorID return items[0], nil } -// UpdateManagerProfile 局部更新经理运营资料和经理中心权限。 -// 未传入的布尔权限不参与 SET,避免后台只编辑联系方式时把现有权限误恢复成默认值。 +// UpdateManagerProfile 局部更新经理运营资料、身份状态和经理中心权限。 +// 未传入的状态/布尔权限不参与 SET,避免后台只编辑联系方式时把现有开关误恢复成默认值。 func (r *Reader) UpdateManagerProfile(ctx context.Context, userID int64, actorID int64, req updateManagerRequest) (*ManagerListItem, error) { if r == nil || r.db == nil { return nil, errUserDBNotConfigured() @@ -671,6 +671,9 @@ func (r *Reader) UpdateManagerProfile(ctx context.Context, userID int64, actorID assignments = append(assignments, "contact_info = ?") args = append(args, contact) } + if err := req.appendStatusAssignment(&assignments, &args); err != nil { + return nil, err + } req.appendPermissionAssignments(&assignments, &args) args = append(args, appctx.FromContext(ctx), userID) // 权限更新只作用于已存在经理;RowsAffected=0 时返回 not found,避免 PUT 顺手创建新经理绕过创建审计。 diff --git a/server/admin/internal/modules/hostorg/reader_test.go b/server/admin/internal/modules/hostorg/reader_test.go new file mode 100644 index 00000000..4d767c49 --- /dev/null +++ b/server/admin/internal/modules/hostorg/reader_test.go @@ -0,0 +1,87 @@ +package hostorg + +import ( + "context" + "testing" + + "hyapp-admin-server/internal/appctx" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUpdateManagerProfilePersistsStatus(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("new sqlmock: %v", err) + } + defer db.Close() + + status := "disabled" + reader := NewReader(db, nil, nil) + ctx := appctx.WithContext(context.Background(), "lalu") + + // 经理状态是 manager_profiles 的身份开关;PUT 传 status 时必须落到 UPDATE,否则前端刷新会重新读到旧状态。 + mock.ExpectExec(`(?s)UPDATE manager_profiles\s+SET updated_at_ms = \?, status = \?\s+WHERE app_code = \? AND user_id = \?`). + WithArgs(sqlmock.AnyArg(), "disabled", "lalu", int64(1003)). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectQuery(`(?s)SELECT COUNT\(DISTINCT mp\.user_id\).*FROM manager_profiles mp`). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(1))) + mock.ExpectQuery(`(?s)SELECT mp\.user_id, COALESCE\(manager\.current_display_user_id, ''\).*FROM manager_profiles mp`). + WillReturnRows(managerListRows(). + AddRow(int64(1003), "1003", "manager", "avatar.png", int64(7), "西亚区", "disabled", "+63", + true, true, true, true, true, true, true, true, true, true, + int64(0), int64(0), int64(1710000000000), int64(1710000001000))) + + item, err := reader.UpdateManagerProfile(ctx, 1003, 7, updateManagerRequest{Status: &status}) + if err != nil { + t.Fatalf("update manager profile: %v", err) + } + if item.Status != "disabled" { + t.Fatalf("manager status = %q, want disabled", item.Status) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("sql expectations: %v", err) + } +} + +func TestUpdateManagerProfileRejectsInvalidStatus(t *testing.T) { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("new sqlmock: %v", err) + } + defer db.Close() + + status := "off" + reader := NewReader(db, nil, nil) + _, err = reader.UpdateManagerProfile(appctx.WithContext(context.Background(), "lalu"), 1003, 7, updateManagerRequest{Status: &status}) + if err == nil || err.Error() != "manager status is invalid" { + t.Fatalf("invalid status error = %v", err) + } +} + +func managerListRows() *sqlmock.Rows { + return sqlmock.NewRows([]string{ + "user_id", + "current_display_user_id", + "username", + "avatar", + "region_id", + "region_name", + "status", + "contact_info", + "can_grant_avatar_frame", + "can_grant_vehicle", + "can_grant_badge", + "can_grant_vip", + "can_update_user_level", + "can_add_bd_leader", + "can_add_admin", + "can_add_superadmin", + "can_block_user", + "can_transfer_user_country", + "bd_leader_count", + "last_invited_at_ms", + "created_at_ms", + "updated_at_ms", + }) +} diff --git a/server/admin/internal/modules/hostorg/request.go b/server/admin/internal/modules/hostorg/request.go index 123d00a5..96bd5a27 100644 --- a/server/admin/internal/modules/hostorg/request.go +++ b/server/admin/internal/modules/hostorg/request.go @@ -83,6 +83,7 @@ type createManagerRequest struct { type updateManagerRequest struct { Contact *string `json:"contact"` + Status *string `json:"status"` CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"` CanGrantVehicle *bool `json:"canGrantVehicle"` CanGrantBadge *bool `json:"canGrantBadge"` @@ -95,6 +96,23 @@ type updateManagerRequest struct { CanTransferCountry *bool `json:"canTransferUserCountry"` } +func (r updateManagerRequest) appendStatusAssignment(assignments *[]string, args *[]any) error { + if r.Status == nil { + return nil + } + status := strings.ToLower(strings.TrimSpace(*r.Status)) + if status == "" { + return nil + } + if status != "active" && status != "disabled" { + return fmt.Errorf("manager status is invalid") + } + // 经理状态属于 manager_profiles 自身身份开关;停用经理只关闭经理中心身份,不改用户主状态或其他 Host/BD 关系。 + *assignments = append(*assignments, "status = ?") + *args = append(*args, status) + return nil +} + type managerPermissions struct { CanGrantAvatarFrame bool CanGrantVehicle bool