团队管理
版本: v0.34.0+ | 组织架构管理 | 层级团队结构
核心特性
- 🏢 多级团队层次: 支持父子团队嵌套,构建完整组织架构树
- 👤 角色体系: lead(负责人)、member(成员)、guest(访客)三级角色划分
- 🔒 删除保护: 存在子团队时禁止删除,保障数据完整性
- 🔄 负责人变更: 自动完成角色升降级,确保数据一致性
- 📊 8 个 IPC 通道: 完整的团队 CRUD 和成员管理 API
- 🔗 深度集成: 与权限引擎、企业审计模块无缝联动
系统架构
┌──────────────┐
│ Renderer │
│ (Vue 页面) │
└──────┬───────┘
│ IPC (8 通道)
▼
┌──────────────────────────────────┐
│ permission-ipc.js │
│ (IPC 路由注册) │
└──────────────┬───────────────────┘
│
┌────────▼────────┐
│ TeamManager │
│ (Singleton) │
│ ┌────────────┐ │
│ │ 团队 CRUD │ │
│ │ 成员管理 │ │
│ │ 层级查询 │ │
│ └────────────┘ │
└────────┬────────┘
│
┌────────▼────────┐
│ SQLite │
│ org_teams │
│ org_team_members│
└─────────────────┘系统概述
团队管理模块(TeamManager)是 ChainlessChain 企业版权限体系的核心组件之一,负责组织内子团队的创建、成员管理以及层级结构维护。该模块基于 SQLite 数据库实现持久化存储,支持多级团队嵌套、团队负责人指定、成员角色划分等企业级组织架构功能。
核心特性:
- 支持多级团队层次结构(父子团队关系)
- 团队负责人自动注册为团队成员
- 成员角色体系:
lead(负责人)、member(成员)、guest(访客) - 团队名称唯一性约束(同一组织内不允许重名)
- 删除保护机制(存在子团队时禁止删除)
- 与权限引擎(Permission Engine)、企业审计(Audit)等模块深度集成
源码位置: desktop-app-vue/src/main/permission/team-manager.js
IPC 注册位置: desktop-app-vue/src/main/permission/permission-ipc.js
核心功能
团队创建与管理
创建团队
通过 createTeam(teamData) 方法创建新团队。系统会自动生成 UUID 作为团队 ID,并记录创建时间戳。如果指定了团队负责人(leadDid),系统会自动将其添加为团队成员,角色设置为 lead。
参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
orgId | String | 是 | 所属组织 ID |
name | String | 是 | 团队名称(同一组织内唯一) |
description | String | 否 | 团队描述 |
parentTeamId | String | 否 | 父团队 ID(用于构建层级结构) |
leadDid | String | 否 | 团队负责人 DID |
leadName | String | 否 | 团队负责人名称 |
avatar | String | 否 | 团队头像 |
settings | Object | 否 | 团队自定义设置(JSON 序列化存储) |
createdBy | String | 否 | 创建者标识(用于记录邀请人) |
返回值:
- 成功:
{ success: true, teamId: '<uuid>' } - 团队名重复:
{ success: false, error: 'TEAM_NAME_EXISTS' }
更新团队
通过 updateTeam(teamId, updates) 方法更新团队信息。系统会自动将 camelCase 字段名转换为 snake_case 数据库字段名,并过滤非法字段。
允许更新的字段:
name- 团队名称description- 团队描述parentTeamId- 父团队 IDleadDid- 负责人 DIDleadName- 负责人名称avatar- 团队头像settings- 团队设置(Object,自动 JSON 序列化)
返回值: { success: true }
删除团队
通过 deleteTeam(teamId) 方法删除团队。系统内置删除保护机制:如果团队下存在子团队,将拒绝删除并返回错误信息。
返回值:
- 成功:
{ success: true } - 存在子团队:
{ success: false, error: 'HAS_SUB_TEAMS', message: 'Team has N sub-teams. Delete them first.' }
设置团队负责人
通过 setLead(teamId, leadDid, leadName) 方法设置或变更团队负责人。该方法会同时完成以下操作:
- 更新
org_teams表中的lead_did和lead_name字段 - 将原负责人的成员角色降级为
member - 将新负责人的成员角色升级为
lead
返回值: { success: true }
成员管理
添加成员
通过 addMember(teamId, memberDid, memberName, role, invitedBy) 方法向团队添加成员。
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
teamId | String | 是 | - | 目标团队 ID |
memberDid | String | 是 | - | 成员 DID 标识 |
memberName | String | 是 | - | 成员名称 |
role | String | 否 | 'member' | 成员角色:lead / member / guest |
invitedBy | String | 否 | null | 邀请人标识 |
返回值:
- 成功:
{ success: true, memberId: '<uuid>' } - 已是成员:
{ success: false, error: 'ALREADY_MEMBER' }
移除成员
通过 removeMember(teamId, memberDid) 方法从团队中移除指定成员。
返回值: { success: true }
查询团队成员
通过 getTeamMembers(teamId) 方法获取指定团队的所有成员列表。结果按角色降序排列(负责人在前),同角色按加入时间升序排列。
返回值:
{
success: true,
members: [
{
id: '<member-record-uuid>',
memberDid: 'did:example:alice',
memberName: 'Alice',
role: 'lead',
joinedAt: 1708900000000,
invitedBy: 'did:example:admin'
},
// ...
]
}层级结构(团队树)
查询团队列表
通过 getTeams(orgId, options) 方法获取组织下的团队列表。支持按父团队 ID 过滤,可用于逐层构建团队树形结构。
参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
orgId | String | 是 | 组织 ID |
options.parentTeamId | String/null | 否 | 父团队 ID。传 null 获取顶级团队,传具体 ID 获取子团队,不传则获取所有团队 |
返回值:
{
success: true,
teams: [
{
id: '<team-uuid>',
name: '研发部',
description: '负责产品研发',
parentTeamId: null,
leadDid: 'did:example:lead1',
leadName: '张三',
avatar: null,
settings: { maxMembers: 50 },
memberCount: 12,
createdAt: 1708900000000,
updatedAt: 1708900000000
},
// ...
]
}构建团队层级树
虽然 TeamManager 本身不提供递归树构建方法,但可以通过 getTeams 的 parentTeamId 过滤参数逐层查询来构建完整的团队层级树:
// 获取顶级团队
const topLevel = await teamManager.getTeams(orgId, { parentTeamId: null });
// 递归获取子团队
async function buildTree(teams) {
for (const team of teams) {
const children = await teamManager.getTeams(orgId, {
parentTeamId: team.id,
});
team.children = children.teams;
if (team.children.length > 0) {
await buildTree(team.children);
}
}
}
await buildTree(topLevel.teams);配置参考
团队管理模块通过数据库进行数据持久化,无需额外配置文件。模块采用单例模式初始化:
const { getTeamManager } = require("./permission/team-manager");
// 传入 database 实例初始化(仅首次需要)
const manager = getTeamManager(database);
// 后续调用无需再传 database
const manager = getTeamManager();团队的自定义配置通过 settings 字段以 JSON 格式存储在数据库中,可用于存储团队级别的个性化设置,例如:
{
"maxMembers": 50,
"allowGuestAccess": true,
"notificationPreferences": {
"email": true,
"inApp": true
}
}API 参考
TeamManager 类方法
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
createTeam(teamData) | { orgId, name, description?, parentTeamId?, leadDid?, leadName?, avatar?, settings?, createdBy? } | { success, teamId } 或 { success: false, error: 'TEAM_NAME_EXISTS' } | 创建团队,自动添加负责人为成员 |
updateTeam(teamId, updates) | teamId: string, updates: object | { success: true } | 更新团队信息,自动 camelCase 转 snake_case |
deleteTeam(teamId) | teamId: string | { success } 或 { success: false, error: 'HAS_SUB_TEAMS' } | 删除团队(有子团队时拒绝) |
setLead(teamId, leadDid, leadName) | teamId: string, leadDid: string, leadName: string | { success: true } | 设置团队负责人并更新成员角色 |
addMember(teamId, did, name, role?, invitedBy?) | teamId: string, did: string, name: string, role?: string, invitedBy?: string | { success, memberId } 或 { success: false, error: 'ALREADY_MEMBER' } | 添加团队成员 |
removeMember(teamId, did) | teamId: string, did: string | { success: true } | 移除团队成员 |
getTeams(orgId, options?) | orgId: string, options?: { parentTeamId? } | { success, teams: [...] } | 查询团队列表,附带成员数量 |
getTeamMembers(teamId) | teamId: string | { success, members: [...] } | 查询团队成员列表 |
工厂函数
| 函数 | 参数 | 说明 |
|---|---|---|
getTeamManager(database?) | database?: Database | 获取 TeamManager 单例。首次调用需传入 database 实例 |
IPC 接口
团队管理模块通过 permission-ipc.js 注册了 8 个 IPC 通道,供渲染进程调用:
| IPC 通道 | 参数 | 说明 |
|---|---|---|
team:create-team | { orgId, name, description?, parentTeamId?, leadDid?, leadName?, avatar?, settings?, createdBy? } | 创建团队 |
team:update-team | { teamId, updates: { name?, description?, parentTeamId?, leadDid?, leadName?, avatar?, settings? } } | 更新团队 |
team:delete-team | { teamId } | 删除团队 |
team:add-member | { teamId, memberDid, memberName, role?, invitedBy? } | 添加成员 |
team:remove-member | { teamId, memberDid } | 移除成员 |
team:set-lead | { teamId, leadDid, leadName } | 设置团队负责人 |
team:get-teams | { orgId, options?: { parentTeamId? } } | 查询团队列表 |
team:get-team-members | { teamId } | 查询团队成员 |
渲染进程调用示例:
// 创建团队
const result = await window.electronAPI.invoke("team:create-team", {
orgId: "org-001",
name: "前端开发组",
description: "负责 Web 前端开发",
leadDid: "did:example:alice",
leadName: "Alice",
});
// 查询团队列表
const teams = await window.electronAPI.invoke("team:get-teams", {
orgId: "org-001",
options: { parentTeamId: null },
});数据库 Schema
org_teams 表
存储团队基本信息,支持通过 parent_team_id 自引用构建层级结构。
CREATE TABLE IF NOT EXISTS org_teams (
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
parent_team_id TEXT,
lead_did TEXT,
lead_name TEXT,
avatar TEXT,
settings TEXT, -- JSON 格式的自定义设置
team_type TEXT DEFAULT 'team', -- Phase 6 新增:团队类型(team/department)
created_at INTEGER NOT NULL, -- 时间戳(毫秒)
updated_at INTEGER NOT NULL, -- 时间戳(毫秒)
FOREIGN KEY (parent_team_id) REFERENCES org_teams(id) ON DELETE CASCADE,
UNIQUE(org_id, name) -- 同一组织内团队名称唯一
);org_team_members 表
存储团队成员关系,支持角色划分。
CREATE TABLE IF NOT EXISTS org_team_members (
id TEXT PRIMARY KEY,
team_id TEXT NOT NULL,
member_did TEXT NOT NULL,
member_name TEXT,
team_role TEXT DEFAULT 'member' CHECK(team_role IN ('lead', 'member', 'guest')),
joined_at INTEGER NOT NULL, -- 加入时间戳(毫秒)
invited_by TEXT, -- 邀请人标识
FOREIGN KEY (team_id) REFERENCES org_teams(id) ON DELETE CASCADE,
UNIQUE(team_id, member_did) -- 同一团队内成员唯一
);索引
CREATE INDEX IF NOT EXISTS idx_org_teams_org ON org_teams(org_id);
CREATE INDEX IF NOT EXISTS idx_org_teams_parent ON org_teams(parent_team_id);
CREATE INDEX IF NOT EXISTS idx_org_teams_lead ON org_teams(lead_did);
CREATE INDEX IF NOT EXISTS idx_org_team_members_team ON org_team_members(team_id);
CREATE INDEX IF NOT EXISTS idx_org_team_members_member ON org_team_members(member_did);使用示例
创建组织团队结构
const { getTeamManager } = require("./permission/team-manager");
const manager = getTeamManager(database);
// 1. 创建顶级团队(研发中心)
const devCenter = await manager.createTeam({
orgId: "org-001",
name: "研发中心",
description: "负责全部产品研发工作",
leadDid: "did:example:cto",
leadName: "王总监",
createdBy: "did:example:admin",
});
// => { success: true, teamId: 'abc-123-...' }
// 2. 创建子团队(前端组)
const frontendTeam = await manager.createTeam({
orgId: "org-001",
name: "前端开发组",
description: "Web & 移动端前端开发",
parentTeamId: devCenter.teamId,
leadDid: "did:example:fe-lead",
leadName: "李组长",
settings: { techStack: ["Vue3", "React", "TypeScript"] },
});
// 3. 创建子团队(后端组)
const backendTeam = await manager.createTeam({
orgId: "org-001",
name: "后端开发组",
parentTeamId: devCenter.teamId,
leadDid: "did:example:be-lead",
leadName: "陈组长",
});管理团队成员
// 添加普通成员
await manager.addMember(
frontendTeam.teamId,
"did:example:dev1",
"张工程师",
"member",
"did:example:fe-lead",
);
// 添加访客(只读权限)
await manager.addMember(
frontendTeam.teamId,
"did:example:designer1",
"刘设计师",
"guest",
"did:example:fe-lead",
);
// 查询团队成员
const members = await manager.getTeamMembers(frontendTeam.teamId);
console.log(members);
// => { success: true, members: [
// { memberDid: 'did:example:fe-lead', memberName: '李组长', role: 'lead', ... },
// { memberDid: 'did:example:dev1', memberName: '张工程师', role: 'member', ... },
// { memberDid: 'did:example:designer1', memberName: '刘设计师', role: 'guest', ... }
// ]}
// 移除成员
await manager.removeMember(frontendTeam.teamId, "did:example:designer1");变更团队负责人
// 将团队负责人从李组长变更为张工程师
await manager.setLead(frontendTeam.teamId, "did:example:dev1", "张工程师");
// 此操作会自动将李组长的角色降级为 member,张工程师的角色升级为 lead查询团队层级
// 获取所有顶级团队
const topTeams = await manager.getTeams("org-001", { parentTeamId: null });
// 获取研发中心下的子团队
const subTeams = await manager.getTeams("org-001", {
parentTeamId: devCenter.teamId,
});
// 获取组织下所有团队(不过滤层级)
const allTeams = await manager.getTeams("org-001");处理错误场景
// 创建重名团队
const duplicate = await manager.createTeam({
orgId: "org-001",
name: "前端开发组", // 已存在
leadDid: "did:example:someone",
});
// => { success: false, error: 'TEAM_NAME_EXISTS' }
// 删除含有子团队的团队
const deleteResult = await manager.deleteTeam(devCenter.teamId);
// => { success: false, error: 'HAS_SUB_TEAMS', message: 'Team has 2 sub-teams. Delete them first.' }
// 重复添加成员
const addResult = await manager.addMember(
frontendTeam.teamId,
"did:example:dev1",
"张工程师",
);
// => { success: false, error: 'ALREADY_MEMBER' }故障排除
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
TEAM_NAME_EXISTS | 同一组织内已存在同名团队 | 使用不同的团队名称,或先删除已有同名团队 |
HAS_SUB_TEAMS | 尝试删除的团队下还有子团队 | 先递归删除所有子团队,再删除父团队 |
ALREADY_MEMBER | 成员已在该团队中 | 无需重复添加,如需变更角色请使用 setLead 方法 |
| 团队列表为空 | orgId 不匹配或尚未创建团队 | 检查传入的 orgId 是否正确 |
settings 字段为 null | 创建时未传入 settings 参数 | 通过 updateTeam 方法补充设置 |
| 更新字段未生效 | 字段名不在允许列表中 | 检查字段名是否正确,仅支持 7 个可更新字段 |
调试日志
团队管理模块的日志标签为 [Team],可通过日志系统查看操作记录:
[Team] Created team abc-123-...
[Team] Added member did:example:dev1 to team abc-123-...
[Team] Removed member did:example:dev1 from team abc-123-...
[Team] Set lead did:example:dev1 for team abc-123-...
[Team] Deleted team abc-123-...数据一致性注意事项
- 删除团队时,
org_team_members表中的关联成员记录会通过外键ON DELETE CASCADE自动删除 setLead方法会同时更新org_teams表和org_team_members表,确保数据一致createTeam中自动添加负责人为成员的操作不在事务中,如果添加成员失败,团队已创建但负责人未加入成员列表
相关文档
关键文件
| 文件 | 职责 | 行数 |
|---|---|---|
src/main/permission/team-manager.js | 团队管理核心逻辑(单例模式) | ~300 |
src/main/permission/permission-ipc.js | IPC 通道注册与路由 | ~200 |
src/main/permission/permission-engine.js | RBAC 权限引擎(团队角色联动) | ~450 |
src/renderer/stores/team.ts | 团队 Pinia 状态管理 | ~150 |
