tenant: move admin member queries into service

This commit is contained in:
2025-12-18 18:03:32 +08:00
parent eedb940799
commit 462bde351d
8 changed files with 331 additions and 26 deletions

View File

@@ -6,6 +6,7 @@ import (
"time"
"quyun/v2/app/http/super/dto"
tenantdto "quyun/v2/app/http/tenant/dto"
"quyun/v2/app/requests"
"quyun/v2/database"
"quyun/v2/database/models"
@@ -24,6 +25,81 @@ import (
// @provider
type tenant struct{}
// AdminTenantUsersPage 租户管理员分页查询成员列表(包含用户基础信息)。
func (t *tenant) AdminTenantUsersPage(ctx context.Context, tenantID int64, filter *tenantdto.AdminTenantUserListFilter) (*requests.Pager, error) {
if tenantID <= 0 {
return nil, errors.New("tenant_id must be > 0")
}
if filter == nil {
filter = &tenantdto.AdminTenantUserListFilter{}
}
filter.Pagination.Format()
tbl, query := models.TenantUserQuery.QueryContext(ctx)
conds := []gen.Condition{tbl.TenantID.Eq(tenantID)}
if filter.UserID != nil && *filter.UserID > 0 {
conds = append(conds, tbl.UserID.Eq(*filter.UserID))
}
if filter.Role != nil && *filter.Role != "" {
conds = append(conds, tbl.Role.Contains(string(*filter.Role)))
}
if filter.Status != nil && *filter.Status != "" {
conds = append(conds, tbl.Status.Eq(*filter.Status))
}
if username := filter.UsernameTrimmed(); username != "" {
uTbl, _ := models.UserQuery.QueryContext(ctx)
query = query.LeftJoin(uTbl, uTbl.ID.EqCol(tbl.UserID))
conds = append(conds, uTbl.Username.Like(database.WrapLike(username)))
}
items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
if err != nil {
return nil, err
}
userIDs := make([]int64, 0, len(items))
for _, tu := range items {
if tu == nil {
continue
}
userIDs = append(userIDs, tu.UserID)
}
var users []*models.User
if len(userIDs) > 0 {
uTbl, uQuery := models.UserQuery.QueryContext(ctx)
users, err = uQuery.Where(uTbl.ID.In(userIDs...)).Find()
if err != nil {
return nil, err
}
}
userMap := make(map[int64]*models.User, len(users))
for _, u := range users {
if u == nil {
continue
}
userMap[u.ID] = u
}
out := make([]*tenantdto.AdminTenantUserItem, 0, len(items))
for _, tu := range items {
if tu == nil {
continue
}
out = append(out, &tenantdto.AdminTenantUserItem{
TenantUser: tu,
User: userMap[tu.UserID],
})
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: out,
}, nil
}
func (t *tenant) ContainsUserID(ctx context.Context, tenantID, userID int64) (*models.User, error) {
tbl, query := models.TenantUserQuery.QueryContext(ctx)
@@ -71,6 +147,10 @@ func (t *tenant) RemoveUser(ctx context.Context, tenantID, userID int64) error {
tbl, query := models.TenantUserQuery.QueryContext(ctx)
tenantUser, err := query.Where(tbl.TenantID.Eq(tenantID), tbl.UserID.Eq(userID)).First()
if err != nil {
// 幂等:成员不存在时也返回成功,便于后台重试/批量移除。
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrapf(err, "RemoveUser failed to find, tenantID: %d, userID: %d", tenantID, userID)
}

View File

@@ -5,6 +5,7 @@ import (
"testing"
"quyun/v2/app/commands/testx"
tenantdto "quyun/v2/app/http/tenant/dto"
"quyun/v2/database"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
@@ -15,6 +16,7 @@ import (
_ "go.ipao.vip/atom"
"go.ipao.vip/atom/contracts"
"go.ipao.vip/gen/types"
"go.uber.org/dig"
)
@@ -96,5 +98,85 @@ func (t *TenantTestSuite) Test_SetUserRole() {
So(len(m.Role), ShouldEqual, 1)
So(m.Role[0], ShouldEqual, consts.TenantUserRoleTenantAdmin)
})
Convey("设置为 member 成功", func() {
err := Tenant.SetUserRole(ctx, tenantID, userID, consts.TenantUserRoleMember)
So(err, ShouldBeNil)
m, err := Tenant.FindTenantUser(ctx, tenantID, userID)
So(err, ShouldBeNil)
So(m, ShouldNotBeNil)
So(len(m.Role), ShouldEqual, 1)
So(m.Role[0], ShouldEqual, consts.TenantUserRoleMember)
})
})
}
func (t *TenantTestSuite) Test_RemoveUser() {
Convey("Tenant.RemoveUser", t.T(), func() {
ctx := t.T().Context()
tenantID := int64(1)
userID := int64(2)
database.Truncate(ctx, t.DB, models.TableNameTenantUser)
Convey("移除不存在成员应幂等返回成功", func() {
So(Tenant.RemoveUser(ctx, tenantID, userID), ShouldBeNil)
})
Convey("移除已存在成员成功", func() {
So(Tenant.AddUser(ctx, tenantID, userID), ShouldBeNil)
So(Tenant.RemoveUser(ctx, tenantID, userID), ShouldBeNil)
_, err := Tenant.FindTenantUser(ctx, tenantID, userID)
So(err, ShouldNotBeNil)
})
})
}
func (t *TenantTestSuite) Test_AdminTenantUsersPage() {
Convey("Tenant.AdminTenantUsersPage", t.T(), func() {
ctx := t.T().Context()
tenantID := int64(1)
database.Truncate(ctx, t.DB, models.TableNameTenantUser, models.TableNameUser)
u1 := &models.User{
Username: "u1",
Password: "pw",
Roles: types.NewArray([]consts.Role{consts.RoleUser}),
Status: consts.UserStatusVerified,
}
So(u1.Create(ctx), ShouldBeNil)
u2 := &models.User{
Username: "u2",
Password: "pw",
Roles: types.NewArray([]consts.Role{consts.RoleUser}),
Status: consts.UserStatusVerified,
}
So(u2.Create(ctx), ShouldBeNil)
So(Tenant.AddUser(ctx, tenantID, u1.ID), ShouldBeNil)
So(Tenant.AddUser(ctx, tenantID, u2.ID), ShouldBeNil)
So(Tenant.SetUserRole(ctx, tenantID, u2.ID, consts.TenantUserRoleTenantAdmin), ShouldBeNil)
Convey("不加过滤应返回用户信息", func() {
pager, err := Tenant.AdminTenantUsersPage(ctx, tenantID, &tenantdto.AdminTenantUserListFilter{})
So(err, ShouldBeNil)
So(pager.Total, ShouldEqual, 2)
items, ok := pager.Items.([]*tenantdto.AdminTenantUserItem)
So(ok, ShouldBeTrue)
So(len(items), ShouldEqual, 2)
So(items[0].User, ShouldNotBeNil)
})
Convey("按 role=tenant_admin 过滤", func() {
role := consts.TenantUserRoleTenantAdmin
pager, err := Tenant.AdminTenantUsersPage(ctx, tenantID, &tenantdto.AdminTenantUserListFilter{Role: &role})
So(err, ShouldBeNil)
So(pager.Total, ShouldEqual, 1)
})
})
}