tenant: add member management APIs
This commit is contained in:
26
backend/app/http/tenant/dto/tenant_user_admin.go
Normal file
26
backend/app/http/tenant/dto/tenant_user_admin.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"quyun/v2/app/requests"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AdminTenantUserJoinResponse 返回租户管理员添加成员后的结果。
|
||||||
|
type AdminTenantUserJoinResponse struct {
|
||||||
|
// TenantUser 租户成员关系记录。
|
||||||
|
TenantUser *models.TenantUser `json:"tenant_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminTenantUserRoleUpdateForm 租户成员角色更新表单。
|
||||||
|
type AdminTenantUserRoleUpdateForm struct {
|
||||||
|
// Role 角色:member/tenant_admin。
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminTenantUserListFilter 租户管理员查询成员列表的过滤条件。
|
||||||
|
type AdminTenantUserListFilter struct {
|
||||||
|
// Pagination 分页参数(page/limit)。
|
||||||
|
requests.Pagination `json:",inline" query:",inline"`
|
||||||
|
// UserID 按用户ID过滤(可选)。
|
||||||
|
UserID *int64 `json:"user_id,omitempty" query:"user_id"`
|
||||||
|
}
|
||||||
@@ -60,15 +60,17 @@ func Provide(opts ...opt.Option) error {
|
|||||||
order *order,
|
order *order,
|
||||||
orderAdmin *orderAdmin,
|
orderAdmin *orderAdmin,
|
||||||
orderMe *orderMe,
|
orderMe *orderMe,
|
||||||
|
tenantUserAdmin *tenantUserAdmin,
|
||||||
) (contracts.HttpRoute, error) {
|
) (contracts.HttpRoute, error) {
|
||||||
obj := &Routes{
|
obj := &Routes{
|
||||||
content: content,
|
content: content,
|
||||||
contentAdmin: contentAdmin,
|
contentAdmin: contentAdmin,
|
||||||
me: me,
|
me: me,
|
||||||
middlewares: middlewares,
|
middlewares: middlewares,
|
||||||
order: order,
|
order: order,
|
||||||
orderAdmin: orderAdmin,
|
orderAdmin: orderAdmin,
|
||||||
orderMe: orderMe,
|
orderMe: orderMe,
|
||||||
|
tenantUserAdmin: tenantUserAdmin,
|
||||||
}
|
}
|
||||||
if err := obj.Prepare(); err != nil {
|
if err := obj.Prepare(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -78,5 +80,12 @@ func Provide(opts ...opt.Option) error {
|
|||||||
}, atom.GroupRoutes); err != nil {
|
}, atom.GroupRoutes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := container.Container.Provide(func() (*tenantUserAdmin, error) {
|
||||||
|
obj := &tenantUserAdmin{}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ type Routes struct {
|
|||||||
log *log.Entry `inject:"false"`
|
log *log.Entry `inject:"false"`
|
||||||
middlewares *middlewares.Middlewares
|
middlewares *middlewares.Middlewares
|
||||||
// Controller instances
|
// Controller instances
|
||||||
content *content
|
content *content
|
||||||
contentAdmin *contentAdmin
|
contentAdmin *contentAdmin
|
||||||
me *me
|
me *me
|
||||||
order *order
|
order *order
|
||||||
orderAdmin *orderAdmin
|
orderAdmin *orderAdmin
|
||||||
orderMe *orderMe
|
orderMe *orderMe
|
||||||
|
tenantUserAdmin *tenantUserAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare initializes the routes provider with logging configuration.
|
// Prepare initializes the routes provider with logging configuration.
|
||||||
@@ -184,6 +185,29 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
Local[*models.User]("user"),
|
Local[*models.User]("user"),
|
||||||
PathParam[int64]("orderID"),
|
PathParam[int64]("orderID"),
|
||||||
))
|
))
|
||||||
|
// Register routes for controller: tenantUserAdmin
|
||||||
|
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/admin/users -> tenantUserAdmin.adminTenantUsers")
|
||||||
|
router.Get("/t/:tenantCode/v1/admin/users"[len(r.Path()):], DataFunc3(
|
||||||
|
r.tenantUserAdmin.adminTenantUsers,
|
||||||
|
Local[*models.Tenant]("tenant"),
|
||||||
|
Local[*models.TenantUser]("tenant_user"),
|
||||||
|
Query[dto.AdminTenantUserListFilter]("filter"),
|
||||||
|
))
|
||||||
|
r.log.Debugf("Registering route: Patch /t/:tenantCode/v1/admin/users/:userID/role -> tenantUserAdmin.adminSetUserRole")
|
||||||
|
router.Patch("/t/:tenantCode/v1/admin/users/:userID/role"[len(r.Path()):], DataFunc4(
|
||||||
|
r.tenantUserAdmin.adminSetUserRole,
|
||||||
|
Local[*models.Tenant]("tenant"),
|
||||||
|
Local[*models.TenantUser]("tenant_user"),
|
||||||
|
PathParam[int64]("userID"),
|
||||||
|
Body[dto.AdminTenantUserRoleUpdateForm]("form"),
|
||||||
|
))
|
||||||
|
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/admin/users/:userID/join -> tenantUserAdmin.adminJoinUser")
|
||||||
|
router.Post("/t/:tenantCode/v1/admin/users/:userID/join"[len(r.Path()):], DataFunc3(
|
||||||
|
r.tenantUserAdmin.adminJoinUser,
|
||||||
|
Local[*models.Tenant]("tenant"),
|
||||||
|
Local[*models.TenantUser]("tenant_user"),
|
||||||
|
PathParam[int64]("userID"),
|
||||||
|
))
|
||||||
|
|
||||||
r.log.Info("Successfully registered all routes")
|
r.log.Info("Successfully registered all routes")
|
||||||
}
|
}
|
||||||
|
|||||||
183
backend/app/http/tenant/tenant_user_admin.go
Normal file
183
backend/app/http/tenant/tenant_user_admin.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package tenant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
|
"quyun/v2/app/http/tenant/dto"
|
||||||
|
"quyun/v2/app/requests"
|
||||||
|
"quyun/v2/app/services"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"go.ipao.vip/gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tenantUserAdmin provides tenant-admin member management endpoints.
|
||||||
|
//
|
||||||
|
// @provider
|
||||||
|
type tenantUserAdmin struct{}
|
||||||
|
|
||||||
|
// adminJoinUser
|
||||||
|
//
|
||||||
|
// @Summary 添加租户成员(租户管理)
|
||||||
|
// @Tags Tenant
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param tenantCode path string true "Tenant Code"
|
||||||
|
// @Param userID path int64 true "UserID"
|
||||||
|
// @Success 200 {object} dto.AdminTenantUserJoinResponse
|
||||||
|
//
|
||||||
|
// @Router /t/:tenantCode/v1/admin/users/:userID/join [post]
|
||||||
|
// @Bind tenant local key(tenant)
|
||||||
|
// @Bind tenantUser local key(tenant_user)
|
||||||
|
// @Bind userID path
|
||||||
|
func (*tenantUserAdmin) adminJoinUser(
|
||||||
|
ctx fiber.Ctx,
|
||||||
|
tenant *models.Tenant,
|
||||||
|
tenantUser *models.TenantUser,
|
||||||
|
userID int64,
|
||||||
|
) (*dto.AdminTenantUserJoinResponse, error) {
|
||||||
|
if err := requireTenantAdmin(tenantUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userID <= 0 {
|
||||||
|
return nil, errorx.ErrInvalidParameter.WithMsg("user_id must be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"tenant_id": tenant.ID,
|
||||||
|
"operator_user_id": tenantUser.UserID,
|
||||||
|
"target_user_id": userID,
|
||||||
|
"operator_is_admin": true,
|
||||||
|
}).Info("tenant.admin.users.join")
|
||||||
|
|
||||||
|
// 关键逻辑:以 TenantUser 为准创建成员关系;服务层保证幂等(已存在则不重复创建)。
|
||||||
|
if err := services.Tenant.AddUser(ctx.Context(), tenant.ID, userID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := services.Tenant.FindTenantUser(ctx.Context(), tenant.ID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dto.AdminTenantUserJoinResponse{TenantUser: m}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminSetUserRole
|
||||||
|
//
|
||||||
|
// @Summary 设置成员角色(租户管理)
|
||||||
|
// @Tags Tenant
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param tenantCode path string true "Tenant Code"
|
||||||
|
// @Param userID path int64 true "UserID"
|
||||||
|
// @Param form body dto.AdminTenantUserRoleUpdateForm true "Form"
|
||||||
|
// @Success 200 {object} dto.AdminTenantUserJoinResponse
|
||||||
|
//
|
||||||
|
// @Router /t/:tenantCode/v1/admin/users/:userID/role [patch]
|
||||||
|
// @Bind tenant local key(tenant)
|
||||||
|
// @Bind tenantUser local key(tenant_user)
|
||||||
|
// @Bind userID path
|
||||||
|
// @Bind form body
|
||||||
|
func (*tenantUserAdmin) adminSetUserRole(
|
||||||
|
ctx fiber.Ctx,
|
||||||
|
tenant *models.Tenant,
|
||||||
|
tenantUser *models.TenantUser,
|
||||||
|
userID int64,
|
||||||
|
form *dto.AdminTenantUserRoleUpdateForm,
|
||||||
|
) (*dto.AdminTenantUserJoinResponse, error) {
|
||||||
|
if err := requireTenantAdmin(tenantUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userID <= 0 || form == nil {
|
||||||
|
return nil, errorx.ErrInvalidParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
roleStr := strings.TrimSpace(form.Role)
|
||||||
|
if roleStr == "" {
|
||||||
|
return nil, errorx.ErrInvalidParameter.WithMsg("role is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var role consts.TenantUserRole
|
||||||
|
switch roleStr {
|
||||||
|
case string(consts.TenantUserRoleMember):
|
||||||
|
role = consts.TenantUserRoleMember
|
||||||
|
case string(consts.TenantUserRoleTenantAdmin):
|
||||||
|
role = consts.TenantUserRoleTenantAdmin
|
||||||
|
default:
|
||||||
|
return nil, errorx.ErrInvalidParameter.WithMsg("invalid role")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"tenant_id": tenant.ID,
|
||||||
|
"operator_user_id": tenantUser.UserID,
|
||||||
|
"target_user_id": userID,
|
||||||
|
"role": role,
|
||||||
|
}).Info("tenant.admin.users.set_role")
|
||||||
|
|
||||||
|
if err := services.Tenant.SetUserRole(ctx.Context(), tenant.ID, userID, role); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := services.Tenant.FindTenantUser(ctx.Context(), tenant.ID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dto.AdminTenantUserJoinResponse{TenantUser: m}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminTenantUsers
|
||||||
|
//
|
||||||
|
// @Summary 成员列表(租户管理)
|
||||||
|
// @Tags Tenant
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param tenantCode path string true "Tenant Code"
|
||||||
|
// @Param filter query dto.AdminTenantUserListFilter true "Filter"
|
||||||
|
// @Success 200 {object} requests.Pager{items=models.TenantUser}
|
||||||
|
//
|
||||||
|
// @Router /t/:tenantCode/v1/admin/users [get]
|
||||||
|
// @Bind tenant local key(tenant)
|
||||||
|
// @Bind tenantUser local key(tenant_user)
|
||||||
|
// @Bind filter query
|
||||||
|
func (*tenantUserAdmin) adminTenantUsers(
|
||||||
|
ctx fiber.Ctx,
|
||||||
|
tenant *models.Tenant,
|
||||||
|
tenantUser *models.TenantUser,
|
||||||
|
filter *dto.AdminTenantUserListFilter,
|
||||||
|
) (*requests.Pager, error) {
|
||||||
|
if err := requireTenantAdmin(tenantUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if filter == nil {
|
||||||
|
filter = &dto.AdminTenantUserListFilter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"tenant_id": tenant.ID,
|
||||||
|
"user_id": tenantUser.UserID,
|
||||||
|
"query_uid": filter.UserID,
|
||||||
|
}).Info("tenant.admin.users.list")
|
||||||
|
|
||||||
|
filter.Pagination.Format()
|
||||||
|
|
||||||
|
tbl, query := models.TenantUserQuery.QueryContext(ctx.Context())
|
||||||
|
conds := []gen.Condition{tbl.TenantID.Eq(tenant.ID)}
|
||||||
|
if filter.UserID != nil && *filter.UserID > 0 {
|
||||||
|
conds = append(conds, tbl.UserID.Eq(*filter.UserID))
|
||||||
|
}
|
||||||
|
|
||||||
|
items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: filter.Pagination,
|
||||||
|
Total: total,
|
||||||
|
Items: items,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"go.ipao.vip/gen"
|
"go.ipao.vip/gen"
|
||||||
|
"go.ipao.vip/gen/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tenant implements tenant-related domain operations.
|
// tenant implements tenant-related domain operations.
|
||||||
@@ -35,9 +37,27 @@ func (t *tenant) ContainsUserID(ctx context.Context, tenantID, userID int64) (*m
|
|||||||
|
|
||||||
// AddUser
|
// AddUser
|
||||||
func (t *tenant) AddUser(ctx context.Context, tenantID, userID int64) error {
|
func (t *tenant) AddUser(ctx context.Context, tenantID, userID int64) error {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"tenant_id": tenantID,
|
||||||
|
"user_id": userID,
|
||||||
|
}).Info("services.tenant.add_user")
|
||||||
|
|
||||||
|
// 幂等:若成员关系已存在,则直接返回成功,避免重复插入触发唯一约束错误。
|
||||||
|
tbl, query := models.TenantUserQuery.QueryContext(ctx)
|
||||||
|
_, err := query.Where(tbl.TenantID.Eq(tenantID), tbl.UserID.Eq(userID)).First()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errors.Wrapf(err, "AddUser failed to query existing, tenantID: %d, userID: %d", tenantID, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键默认值:加入租户默认成为 member,并设置为 verified(避免 DB 默认值与枚举不一致导致脏数据)。
|
||||||
tenantUser := &models.TenantUser{
|
tenantUser := &models.TenantUser{
|
||||||
TenantID: tenantID,
|
TenantID: tenantID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
Role: types.NewArray([]consts.TenantUserRole{consts.TenantUserRoleMember}),
|
||||||
|
Status: consts.UserStatusVerified,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tenantUser.Create(ctx); err != nil {
|
if err := tenantUser.Create(ctx); err != nil {
|
||||||
@@ -69,7 +89,8 @@ func (t *tenant) SetUserRole(ctx context.Context, tenantID, userID int64, role .
|
|||||||
return errors.Wrapf(err, "SetUserRole failed to find, tenantID: %d, userID: %d", tenantID, userID)
|
return errors.Wrapf(err, "SetUserRole failed to find, tenantID: %d, userID: %d", tenantID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
tenantUser.Role = role
|
// 角色更新:当前约定 role 数组通常只存一个主角色(member/tenant_admin)。
|
||||||
|
tenantUser.Role = types.NewArray(role)
|
||||||
if _, err := tenantUser.Update(ctx); err != nil {
|
if _, err := tenantUser.Update(ctx); err != nil {
|
||||||
return errors.Wrapf(err, "SetUserRole failed to update, tenantID: %d, userID: %d", tenantID, userID)
|
return errors.Wrapf(err, "SetUserRole failed to update, tenantID: %d, userID: %d", tenantID, userID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"quyun/v2/app/commands/testx"
|
"quyun/v2/app/commands/testx"
|
||||||
"quyun/v2/database"
|
"quyun/v2/database"
|
||||||
"quyun/v2/database/models"
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
"quyun/v2/pkg/utils"
|
"quyun/v2/pkg/utils"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
@@ -48,3 +49,52 @@ func (t *TenantTestSuite) Test_TenantUserCount() {
|
|||||||
t.T().Logf("%s", utils.MustJsonString(result))
|
t.T().Logf("%s", utils.MustJsonString(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TenantTestSuite) Test_AddUser() {
|
||||||
|
Convey("Tenant.AddUser", t.T(), func() {
|
||||||
|
ctx := t.T().Context()
|
||||||
|
tenantID := int64(1)
|
||||||
|
userID := int64(2)
|
||||||
|
|
||||||
|
database.Truncate(ctx, t.DB, models.TableNameTenantUser)
|
||||||
|
|
||||||
|
Convey("首次添加成员成功", func() {
|
||||||
|
err := Tenant.AddUser(ctx, tenantID, userID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
m, err := Tenant.FindTenantUser(ctx, tenantID, userID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(m, ShouldNotBeNil)
|
||||||
|
So(m.TenantID, ShouldEqual, tenantID)
|
||||||
|
So(m.UserID, ShouldEqual, userID)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("重复添加应幂等返回成功", func() {
|
||||||
|
So(Tenant.AddUser(ctx, tenantID, userID), ShouldBeNil)
|
||||||
|
So(Tenant.AddUser(ctx, tenantID, userID), ShouldBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TenantTestSuite) Test_SetUserRole() {
|
||||||
|
Convey("Tenant.SetUserRole", t.T(), func() {
|
||||||
|
ctx := t.T().Context()
|
||||||
|
tenantID := int64(1)
|
||||||
|
userID := int64(2)
|
||||||
|
|
||||||
|
database.Truncate(ctx, t.DB, models.TableNameTenantUser)
|
||||||
|
|
||||||
|
So(Tenant.AddUser(ctx, tenantID, userID), ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("设置为 tenant_admin 成功", func() {
|
||||||
|
err := Tenant.SetUserRole(ctx, tenantID, userID, consts.TenantUserRoleTenantAdmin)
|
||||||
|
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.TenantUserRoleTenantAdmin)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
-- tenant_users.status:历史上默认值为 'active',但代码枚举使用 UserStatus(pending_verify/verified/banned)。
|
||||||
|
-- 为避免新增成员落入未知状态,这里将默认值调整为 'verified',并修正存量 'active' -> 'verified'。
|
||||||
|
ALTER TABLE tenant_users
|
||||||
|
ALTER COLUMN status SET DEFAULT 'verified';
|
||||||
|
|
||||||
|
UPDATE tenant_users
|
||||||
|
SET status = 'verified'
|
||||||
|
WHERE status = 'active';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
-- 回滚:恢复默认值为 'active'(不回滚数据修正)。
|
||||||
|
ALTER TABLE tenant_users
|
||||||
|
ALTER COLUMN status SET DEFAULT 'active';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
@@ -803,6 +803,154 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/t/{tenantCode}/v1/admin/users": {
|
||||||
|
"get": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tenant"
|
||||||
|
],
|
||||||
|
"summary": "成员列表(租户管理)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tenant Code",
|
||||||
|
"name": "tenantCode",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "UserID 按用户ID过滤(可选)。",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/requests.Pager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.TenantUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/t/{tenantCode}/v1/admin/users/{userID}/join": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tenant"
|
||||||
|
],
|
||||||
|
"summary": "添加租户成员(租户管理)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tenant Code",
|
||||||
|
"name": "tenantCode",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "UserID",
|
||||||
|
"name": "userID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.AdminTenantUserJoinResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/t/{tenantCode}/v1/admin/users/{userID}/role": {
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tenant"
|
||||||
|
],
|
||||||
|
"summary": "设置成员角色(租户管理)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tenant Code",
|
||||||
|
"name": "tenantCode",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "UserID",
|
||||||
|
"name": "userID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Form",
|
||||||
|
"name": "form",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.AdminTenantUserRoleUpdateForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.AdminTenantUserJoinResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/t/{tenantCode}/v1/admin/users/{userID}/topup": {
|
"/t/{tenantCode}/v1/admin/users/{userID}/topup": {
|
||||||
"post": {
|
"post": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -1553,6 +1701,28 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.AdminTenantUserJoinResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tenant_user": {
|
||||||
|
"description": "TenantUser 租户成员关系记录。",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/models.TenantUser"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.AdminTenantUserRoleUpdateForm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"role": {
|
||||||
|
"description": "Role 角色:member/tenant_admin。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.AdminTopupForm": {
|
"dto.AdminTopupForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -797,6 +797,154 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/t/{tenantCode}/v1/admin/users": {
|
||||||
|
"get": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tenant"
|
||||||
|
],
|
||||||
|
"summary": "成员列表(租户管理)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tenant Code",
|
||||||
|
"name": "tenantCode",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "UserID 按用户ID过滤(可选)。",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/requests.Pager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.TenantUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/t/{tenantCode}/v1/admin/users/{userID}/join": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tenant"
|
||||||
|
],
|
||||||
|
"summary": "添加租户成员(租户管理)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tenant Code",
|
||||||
|
"name": "tenantCode",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "UserID",
|
||||||
|
"name": "userID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.AdminTenantUserJoinResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/t/{tenantCode}/v1/admin/users/{userID}/role": {
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tenant"
|
||||||
|
],
|
||||||
|
"summary": "设置成员角色(租户管理)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tenant Code",
|
||||||
|
"name": "tenantCode",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "UserID",
|
||||||
|
"name": "userID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Form",
|
||||||
|
"name": "form",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.AdminTenantUserRoleUpdateForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.AdminTenantUserJoinResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/t/{tenantCode}/v1/admin/users/{userID}/topup": {
|
"/t/{tenantCode}/v1/admin/users/{userID}/topup": {
|
||||||
"post": {
|
"post": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -1547,6 +1695,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.AdminTenantUserJoinResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tenant_user": {
|
||||||
|
"description": "TenantUser 租户成员关系记录。",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/models.TenantUser"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.AdminTenantUserRoleUpdateForm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"role": {
|
||||||
|
"description": "Role 角色:member/tenant_admin。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.AdminTopupForm": {
|
"dto.AdminTopupForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -185,6 +185,19 @@ definitions:
|
|||||||
退款原因:建议必填(由业务侧校验);用于审计与追责。
|
退款原因:建议必填(由业务侧校验);用于审计与追责。
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.AdminTenantUserJoinResponse:
|
||||||
|
properties:
|
||||||
|
tenant_user:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/models.TenantUser'
|
||||||
|
description: TenantUser 租户成员关系记录。
|
||||||
|
type: object
|
||||||
|
dto.AdminTenantUserRoleUpdateForm:
|
||||||
|
properties:
|
||||||
|
role:
|
||||||
|
description: Role 角色:member/tenant_admin。
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.AdminTopupForm:
|
dto.AdminTopupForm:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
@@ -1511,6 +1524,102 @@ paths:
|
|||||||
summary: 订单退款(租户管理)
|
summary: 订单退款(租户管理)
|
||||||
tags:
|
tags:
|
||||||
- Tenant
|
- Tenant
|
||||||
|
/t/{tenantCode}/v1/admin/users:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Tenant Code
|
||||||
|
in: path
|
||||||
|
name: tenantCode
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Limit is page size; only values in {10,20,50,100} are accepted
|
||||||
|
(otherwise defaults to 10).
|
||||||
|
in: query
|
||||||
|
name: limit
|
||||||
|
type: integer
|
||||||
|
- description: Page is 1-based page index; values <= 0 are normalized to 1.
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: UserID 按用户ID过滤(可选)。
|
||||||
|
in: query
|
||||||
|
name: user_id
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/requests.Pager'
|
||||||
|
- properties:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.TenantUser'
|
||||||
|
type: object
|
||||||
|
summary: 成员列表(租户管理)
|
||||||
|
tags:
|
||||||
|
- Tenant
|
||||||
|
/t/{tenantCode}/v1/admin/users/{userID}/join:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Tenant Code
|
||||||
|
in: path
|
||||||
|
name: tenantCode
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: UserID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: userID
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.AdminTenantUserJoinResponse'
|
||||||
|
summary: 添加租户成员(租户管理)
|
||||||
|
tags:
|
||||||
|
- Tenant
|
||||||
|
/t/{tenantCode}/v1/admin/users/{userID}/role:
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Tenant Code
|
||||||
|
in: path
|
||||||
|
name: tenantCode
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: UserID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: userID
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Form
|
||||||
|
in: body
|
||||||
|
name: form
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.AdminTenantUserRoleUpdateForm'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.AdminTenantUserJoinResponse'
|
||||||
|
summary: 设置成员角色(租户管理)
|
||||||
|
tags:
|
||||||
|
- Tenant
|
||||||
/t/{tenantCode}/v1/admin/users/{userID}/topup:
|
/t/{tenantCode}/v1/admin/users/{userID}/topup:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -151,3 +151,23 @@ Authorization: Bearer {{ token }}
|
|||||||
"reason": "联调充值",
|
"reason": "联调充值",
|
||||||
"idempotency_key": "topup-{{ topupUserID }}-001"
|
"idempotency_key": "topup-{{ topupUserID }}-001"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### Tenant Admin - Join a user to tenant (add member)
|
||||||
|
@joinUserID = 3
|
||||||
|
POST {{ host }}/t/{{ tenantCode }}/v1/admin/users/{{ joinUserID }}/join
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{ token }}
|
||||||
|
|
||||||
|
### Tenant Admin - Set member role
|
||||||
|
PATCH {{ host }}/t/{{ tenantCode }}/v1/admin/users/{{ joinUserID }}/role
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{ token }}
|
||||||
|
|
||||||
|
{
|
||||||
|
"role": "tenant_admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
### Tenant Admin - Tenant members list
|
||||||
|
GET {{ host }}/t/{{ tenantCode }}/v1/admin/users?page=1&limit=20
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{ token }}
|
||||||
|
|||||||
Reference in New Issue
Block a user