跳转到正文

成员与角色

当前实现限制

WorkspaceUserServiceImpl.queryPoByUserIdAndWorkspaceIdqueryPoByWorkspaceIdForOwner 实际调用必取接口,无结果会抛异常。因此首次加入成员会在检查阶段失败,创建空间或默认初始化可能已先写入空间记录。本文的创建、邀请、加入步骤说明接口顺序;需先修正并验证该调用链才能跑通新成员流程。isAdminisOwnercontains 的缺失成员情况也可能抛异常,不能统一当作 false。详见重点文件与方法

成员关系将一个系统用户与一个空间关联。通过邀请加入创建普通成员或管理员关系;App 创建空间时创建所有者关系。

空间成员的 OWNERADMINMEMBER系统菜单角色是两套独立关系;调整空间成员角色不会自动授予菜单或接口操作权限。

角色和操作范围

角色数据库 codeApp 业务操作
OWNER 所有者1具备管理员操作,可以删除空间、转让普通空间所有权;不能直接退出或被移除
ADMIN 管理员2创建、删除邀请,移除其他非所有者成员,调整其他非所有者成员角色
MEMBER 普通成员3可以主动退出,不能创建邀请或管理其他成员

管理员可以调整另一位管理员的角色或将其移出,也可以生成授予 ADMIN 的邀请。角色调整不能指向自己或所有者,不能直接把角色设为 OWNER;所有权变更使用转让接口。

这里描述的是实际业务方法中的校验,并不意味着所有查询入口都已经按成员身份鉴权。App 成员分页只有空间条件过滤,应用需补充调用者的访问校验。

查询成员

以下路径统一加前缀 /api/app/workspace/user。请求和响应格式沿用接口约定

方法与路径请求体返回数据与范围
POST /contains/workspace/id/{workspaceId}Boolean,检查当前登录用户是否有该空间的成员关系
POST /page/workspace/id/{workspaceId}WorkspaceUserQueryPageModel<WorkspaceUserVo>,路径空间 ID 覆盖请求体中的空间 ID;未检查调用者是否属于该空间

分页请求示例:

json
{
  "page": 1,
  "rows": 10,
  "userId": "20001"
}

不填 userId 时查询空间中的全部成员。筛选只支持 userIdworkspaceId,当前不支持按角色、邀请码或 keywords 搜索;按创建时间和 ID 倒序返回。

成员字段含义
id成员关系 ID,即接口中的 workspaceUserId
userId系统用户 ID,用于移除或转让接口的目标参数
workspaceId所属空间 ID
permission角色枚举名
inviteCode加入时的邀请码;直接创建的关系可以没有该值
workspace列表结果补充的空间简要信息
userInfo列表结果补充的用户资料,缺少对应资料时可为空

基础 VO 还包含创建、修改审计字段。workspaceuserInfo 在列表后处理时补充,不保证管理端按 ID 获取成员时也包含这些对象。

调整角色、转让和退出

仍使用 /api/app/workspace/user 前缀,以下操作不需要 JSON 请求体,成功时无业务数据。

方法与路径实际校验与效果
POST /update/id/{workspaceUserId}/permission/{permission}调用者须是该关系所属空间的所有者或管理员;只能在其他非所有者成员的 ADMINMEMBER 之间调整
PUT /transfer/workspace/id/{workspaceId}/user/{userId}调用者须是所有者,目标须是已加入的非所有者成员;默认空间不可转让;原所有者变为 ADMIN,目标变为 OWNER
DELETE /remove/workspace/id/{workspaceId}/user/{userId}调用者须是所有者或管理员,不能移除自己或所有者;目标不在空间时直接返回
DELETE /leave/workspace/id/{workspaceId}退出者固定为当前用户;所有者不能退出,没有成员关系时直接返回

例如,调整角色使用成员列表的 id,请求 POST /api/app/workspace/user/update/id/{workspaceUserId}/permission/ADMIN;转让使用成员的 userId。两种 ID 不可混用。

成员退出或被移除只删除关系,不清理其业务数据,也不撤销已存在的邀请。如果邀请码仍有效,该用户可以再次通过邀请加入。

管理后台维护成员

引入管理端 Controller 后,路径前缀为 /api/admin/workspace/user

方法与路径请求体返回数据
POST /allWorkspaceUserQueryWorkspaceUserVo[]
POST /pageWorkspaceUserQueryPageModel<WorkspaceUserVo>
POST /mapWorkspaceUserQueryWorkspaceUserMapVo[],不含关联空间、用户资料对象
GET /id/{id}WorkspaceUserVo
POST /WorkspaceUserDtoWorkspaceUserVo
PUT /id/{id}WorkspaceUserDtoWorkspaceUserVo
DELETE /id/{id}无业务数据,要求二次确认
DELETE /ids{"ids":["30001","30002"]}无业务数据,要求二次确认

这里的 {id}ids 都是成员关系 ID。WorkspaceUserDto 字段为 userIdworkspaceIdpermissioninviteCode

管理端使用 WORKSPACE_USER 模块权限及类级 WORKSPACE 查询/查看权限声明;allpage 的方法级查询权限属于 WORKSPACE。这些是后台操作权限,不是空间角色校验。

当前管理端通用 CRUD 没有调用 joinremovetransferOwner 的业务校验,DTO 也未补充必填字段和角色约束;直接新增或删除不会自动保证成员唯一、所有者不可删除等规则。其 DTO 更新还受通用转换递归问题影响。管理后台应在应用服务层补齐约束后使用,不能将这些 CRUD 当成 App 成员流程的等价入口。

并发与服务编排

WorkspaceUserService 提供 joinremoveleaveisAdminisOwnertransferOwner 和两种 updatePermission 重载。直接调用时要理解权限校验位置:remove 只保护所有者,updatePermission 直接更新字段;对操作者的管理员校验主要在 App Controller,应用自定义入口需自行补齐。

加入成员按“空间 ID + 用户 ID”加锁并检查重复关系;转让按空间 ID 加锁;默认空间初始化按用户 ID 加锁。锁通过 StoreLockManager 实现,单实例可用本地 Store,多实例应统一共享后端和命名空间。数据库索引没有为空间与用户组合提供唯一约束,绕过这些流程的写入不受防重锁保护。

锁不等于数据库事务。当前 App 创建空间后再加入所有者、所有权转让的两次角色更新、默认空间初始化,都没有在模块方法上声明事务。需要这些步骤共同成功或回滚时,在应用服务中通过 Spring 事务代理编排调用;默认空间示例见空间管理

使用 Store 自动配置的事务感知释放策略时,加锁前已经进入的同步事务会将锁保留到提交或回滚完成。同一事务通过同一管理器再次取得相同键时逻辑重入;具体适用条件见事务中的释放时机