外观
成员与角色
当前实现限制
WorkspaceUserServiceImpl.queryPoByUserIdAndWorkspaceId 与 queryPoByWorkspaceIdForOwner 实际调用必取接口,无结果会抛异常。因此首次加入成员会在检查阶段失败,创建空间或默认初始化可能已先写入空间记录。本文的创建、邀请、加入步骤说明接口顺序;需先修正并验证该调用链才能跑通新成员流程。isAdmin、isOwner、contains 的缺失成员情况也可能抛异常,不能统一当作 false。详见重点文件与方法。
成员关系将一个系统用户与一个空间关联。通过邀请加入创建普通成员或管理员关系;App 创建空间时创建所有者关系。
空间成员的 OWNER、ADMIN、MEMBER 与系统菜单角色是两套独立关系;调整空间成员角色不会自动授予菜单或接口操作权限。
角色和操作范围
| 角色 | 数据库 code | App 业务操作 |
|---|---|---|
OWNER 所有者 | 1 | 具备管理员操作,可以删除空间、转让普通空间所有权;不能直接退出或被移除 |
ADMIN 管理员 | 2 | 创建、删除邀请,移除其他非所有者成员,调整其他非所有者成员角色 |
MEMBER 普通成员 | 3 | 可以主动退出,不能创建邀请或管理其他成员 |
管理员可以调整另一位管理员的角色或将其移出,也可以生成授予 ADMIN 的邀请。角色调整不能指向自己或所有者,不能直接把角色设为 OWNER;所有权变更使用转让接口。
这里描述的是实际业务方法中的校验,并不意味着所有查询入口都已经按成员身份鉴权。App 成员分页只有空间条件过滤,应用需补充调用者的访问校验。
查询成员
以下路径统一加前缀 /api/app/workspace/user。请求和响应格式沿用接口约定。
| 方法与路径 | 请求体 | 返回数据与范围 |
|---|---|---|
POST /contains/workspace/id/{workspaceId} | 无 | Boolean,检查当前登录用户是否有该空间的成员关系 |
POST /page/workspace/id/{workspaceId} | WorkspaceUserQuery | PageModel<WorkspaceUserVo>,路径空间 ID 覆盖请求体中的空间 ID;未检查调用者是否属于该空间 |
分页请求示例:
json
{
"page": 1,
"rows": 10,
"userId": "20001"
}不填 userId 时查询空间中的全部成员。筛选只支持 userId、workspaceId,当前不支持按角色、邀请码或 keywords 搜索;按创建时间和 ID 倒序返回。
| 成员字段 | 含义 |
|---|---|
id | 成员关系 ID,即接口中的 workspaceUserId |
userId | 系统用户 ID,用于移除或转让接口的目标参数 |
workspaceId | 所属空间 ID |
permission | 角色枚举名 |
inviteCode | 加入时的邀请码;直接创建的关系可以没有该值 |
workspace | 列表结果补充的空间简要信息 |
userInfo | 列表结果补充的用户资料,缺少对应资料时可为空 |
基础 VO 还包含创建、修改审计字段。workspace、userInfo 在列表后处理时补充,不保证管理端按 ID 获取成员时也包含这些对象。
调整角色、转让和退出
仍使用 /api/app/workspace/user 前缀,以下操作不需要 JSON 请求体,成功时无业务数据。
| 方法与路径 | 实际校验与效果 |
|---|---|
POST /update/id/{workspaceUserId}/permission/{permission} | 调用者须是该关系所属空间的所有者或管理员;只能在其他非所有者成员的 ADMIN、MEMBER 之间调整 |
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 /all | WorkspaceUserQuery | WorkspaceUserVo[] |
POST /page | WorkspaceUserQuery | PageModel<WorkspaceUserVo> |
POST /map | WorkspaceUserQuery | WorkspaceUserMapVo[],不含关联空间、用户资料对象 |
GET /id/{id} | 无 | WorkspaceUserVo |
POST / | WorkspaceUserDto | WorkspaceUserVo |
PUT /id/{id} | WorkspaceUserDto | WorkspaceUserVo |
DELETE /id/{id} | 无 | 无业务数据,要求二次确认 |
DELETE /ids | {"ids":["30001","30002"]} | 无业务数据,要求二次确认 |
这里的 {id} 和 ids 都是成员关系 ID。WorkspaceUserDto 字段为 userId、workspaceId、permission、inviteCode。
管理端使用 WORKSPACE_USER 模块权限及类级 WORKSPACE 查询/查看权限声明;all、page 的方法级查询权限属于 WORKSPACE。这些是后台操作权限,不是空间角色校验。
当前管理端通用 CRUD 没有调用 join、remove、transferOwner 的业务校验,DTO 也未补充必填字段和角色约束;直接新增或删除不会自动保证成员唯一、所有者不可删除等规则。其 DTO 更新还受通用转换递归问题影响。管理后台应在应用服务层补齐约束后使用,不能将这些 CRUD 当成 App 成员流程的等价入口。
并发与服务编排
WorkspaceUserService 提供 join、remove、leave、isAdmin、isOwner、transferOwner 和两种 updatePermission 重载。直接调用时要理解权限校验位置:remove 只保护所有者,updatePermission 直接更新字段;对操作者的管理员校验主要在 App Controller,应用自定义入口需自行补齐。
加入成员按“空间 ID + 用户 ID”加锁并检查重复关系;转让按空间 ID 加锁;默认空间初始化按用户 ID 加锁。锁通过 StoreLockManager 实现,单实例可用本地 Store,多实例应统一共享后端和命名空间。数据库索引没有为空间与用户组合提供唯一约束,绕过这些流程的写入不受防重锁保护。
锁不等于数据库事务。当前 App 创建空间后再加入所有者、所有权转让的两次角色更新、默认空间初始化,都没有在模块方法上声明事务。需要这些步骤共同成功或回滚时,在应用服务中通过 Spring 事务代理编排调用;默认空间示例见空间管理。
使用 Store 自动配置的事务感知释放策略时,加锁前已经进入的同步事务会将锁保留到提交或回滚完成。同一事务通过同一管理器再次取得相同键时逻辑重入;具体适用条件见事务中的释放时机。