AI 原生营销矩阵系统:账号与素材分组协同管理技术实现
摘要:在企业级营销矩阵的规模化运营中,当账号和素材数量突破百级规模后,传统的扁平式管理方式会出现管理混乱、权限不清、资源复用困难、操作错误率高等问题。分组协同管理作为矩阵系统的核心能力,通过业务化的组织架构映射和精细化的权限控制,实现了账号与素材的有序管理和高效协同。本文从工程实践角度,深入拆解行业典型技术架构落地实践中的分组协同管理系统,详细讲解多维度分组模型、权限继承机制、素材 - 账号双向关联、分布式批量操作引擎、跨分组数据隔离与共享等核心技术的实现细节,并分享企业级矩阵管理的最佳实践。
一、引言:规模化矩阵运营的管理痛点
随着企业营销矩阵的不断扩张,账号和素材数量呈指数级增长,传统的管理方式已经无法满足业务需求,暴露出以下根本性痛点:
- 管理混乱无序:数百个账号和数万条素材混杂在一起,没有清晰的分类和组织,查找和管理困难
- 权限控制粗放:只能实现系统级别的权限控制,无法针对不同业务线、不同区域进行精细化的权限隔离
- 资源复用率低:优质素材无法在合适的账号之间高效流转,导致重复制作和资源浪费
- 操作错误率高:容易出现发错账号、用错素材的情况,给企业带来不必要的损失
- 批量操作困难:无法针对特定业务线或区域的账号进行批量操作,效率低下
- 数据统计困难:难以按业务维度进行数据统计和分析,无法准确评估不同业务线的运营效果
为了解决这些问题,行业领先的解决方案普遍构建了业务化分组协同管理系统,将企业的组织架构映射到系统中,实现了账号、素材、人员、数据的一体化管理。以星链引擎为代表的行业实践,支持按业务线、品牌、区域、品类等多个维度进行分组,实现了分组内数据隔离和跨分组资源共享,大幅提升了矩阵运营的效率和规范性。
二、分组协同管理系统的整体架构
行业典型的分组协同管理系统采用分层模块化架构,将管理逻辑与业务逻辑解耦,实现了灵活的分组配置和高效的协同操作。
2.1 整体技术架构
plaintext
┌─────────────────────────────────────────────────────────┐
│ 前端展示层 │
│ ├─ 分组树状展示 ├─ 拖拽式分组管理 │
│ ├─ 权限配置界面 ├─ 批量操作控制台 │
│ └─ 数据统计看板 └─ 操作日志查询 │
├─────────────────────────────────────────────────────────┤
│ 业务应用层 │
│ ├─ 账号分组管理 ├─ 素材分组管理 │
│ ├─ 权限分配管理 ├─ 批量操作管理 │
│ ├─ 关联映射管理 ├─ 数据统计分析 │
│ └─ 共享资源管理 └─ 操作审计管理 │
├─────────────────────────────────────────────────────────┤
│ 核心能力层 │
│ ├─ 分组模型引擎 ├─ 权限控制引擎 │
│ ├─ 关联映射引擎 ├─ 批量操作引擎 │
│ └─ 数据隔离引擎 └─ 缓存加速引擎 │
├─────────────────────────────────────────────────────────┤
│ 基础数据层 │
│ ├─ 分组信息表 ├─ 账号信息表 │
│ ├─ 素材信息表 ├─ 权限配置表 │
│ ├─ 关联关系表 ├─ 操作日志表 │
│ └─ 共享资源表 └─ 统计数据表 │
└─────────────────────────────────────────────────────────┘
2.2 核心设计原则
- 业务化映射:分组结构与企业的实际业务组织架构保持一致,降低用户的学习成本
- 多维度支持:支持按业务线、品牌、区域、品类、运营人员等多个维度进行分组
- 权限继承:子分组自动继承父分组的权限,同时支持权限的覆盖和例外配置
- 数据隔离:不同分组的数据默认相互隔离,保障业务数据的安全性
- 灵活共享:支持跨分组的资源共享和权限申请,提高资源的复用率
- 可追溯性:所有操作都有完整的日志记录,实现全流程可追溯
三、核心模块技术实现
3.1 多维度分组模型设计
多维度分组模型是分组协同管理系统的基础,能够灵活适应不同企业的组织架构和业务需求。
技术实现:
- 采用树形结构存储分组信息,支持无限级分组嵌套
- 每个分组可以设置多个维度标签,如业务线、品牌、区域、品类等
- 支持分组的创建、修改、删除、移动、合并等操作
- 实现分组的软删除机制,避免误删除导致的数据丢失
- 提供分组的元数据管理,支持自定义分组属性
数据库设计示例(MySQL):
sql
-- 分组信息表
CREATE TABLE `sys_group` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '分组ID',
`parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父分组ID',
`name` varchar(100) NOT NULL COMMENT '分组名称',
`description` varchar(500) DEFAULT NULL COMMENT '分组描述',
`type` varchar(50) NOT NULL COMMENT '分组类型:business-业务线, brand-品牌, region-区域, category-品类',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序号',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用, 1-启用',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_user` bigint NOT NULL COMMENT '创建人ID',
`is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除:0-否, 1-是',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_type` (`type`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分组信息表';
-- 分组标签表
CREATE TABLE `sys_group_tag` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`group_id` bigint NOT NULL COMMENT '分组ID',
`tag_key` varchar(50) NOT NULL COMMENT '标签键',
`tag_value` varchar(200) NOT NULL COMMENT '标签值',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_tag` (`group_id`, `tag_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分组标签表';
-- 账号分组关联表
CREATE TABLE `account_group_rel` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`account_id` bigint NOT NULL COMMENT '账号ID',
`group_id` bigint NOT NULL COMMENT '分组ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_account_group` (`account_id`, `group_id`),
KEY `idx_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号分组关联表';
-- 素材分组关联表
CREATE TABLE `material_group_rel` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`material_id` bigint NOT NULL COMMENT '素材ID',
`group_id` bigint NOT NULL COMMENT '分组ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_material_group` (`material_id`, `group_id`),
KEY `idx_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='素材分组关联表';
代码示例:分组树查询实现(Java)
java
运行
@Service
public class GroupService {
@Autowired
private GroupRepository groupRepository;
// 获取分组树
public List<GroupVO> getGroupTree(String type) {
// 查询所有启用的分组
List<SysGroup> groups = groupRepository.findByTypeAndStatusAndIsDeleted(type, 1, 0);
// 构建分组树
Map<Long, List<GroupVO>> childrenMap = new HashMap<>();
List<GroupVO> rootGroups = new ArrayList<>();
for (SysGroup group : groups) {
GroupVO vo = convertToVO(group);
if (group.getParentId() == 0) {
rootGroups.add(vo);
} else {
childrenMap.computeIfAbsent(group.getParentId(), k -> new ArrayList<>()).add(vo);
}
}
// 递归设置子分组
setChildren(rootGroups, childrenMap);
return rootGroups;
}
// 递归设置子分组
private void setChildren(List<GroupVO> groups, Map<Long, List<GroupVO>> childrenMap) {
for (GroupVO group : groups) {
List<GroupVO> children = childrenMap.get(group.getId());
if (children != null && !children.isEmpty()) {
group.setChildren(children);
setChildren(children, childrenMap);
}
}
}
// 转换为VO对象
private GroupVO convertToVO(SysGroup group) {
GroupVO vo = new GroupVO();
vo.setId(group.getId());
vo.setName(group.getName());
vo.setDescription(group.getDescription());
vo.setType(group.getType());
vo.setSort(group.getSort());
vo.setStatus(group.getStatus());
return vo;
}
}
3.2 基于分组的权限继承机制
基于分组的权限继承机制是实现精细化权限控制的核心,能够灵活适配不同企业的组织架构和权限需求。
技术实现:
- 扩展传统的 RBAC 模型,增加分组维度的权限控制
- 实现权限继承机制,子分组自动继承父分组的所有权限
- 支持权限覆盖,子分组可以覆盖父分组的某些权限
- 支持权限例外,可以为特定用户或角色设置例外权限
- 实现数据级权限控制,用户只能看到自己权限范围内的数据
权限模型设计:
plaintext
┌─────────────────────────────────────────────────────────┐
│ 用户 │
└───────────┬─────────────────────────────────────────────┘
│
┌───────────▼─────────────────────────────────────────────┐
│ 角色 │
└───────────┬─────────────────────────────────────────────┘
│
┌───────────▼─────────────────────────────────────────────┐
│ 权限 │
├─────────────────────────────────────────────────────────┤
│ 功能权限:菜单、按钮、接口 │
│ 数据权限:分组、账号、素材 │
│ 操作权限:查看、新增、编辑、删除、发布 │
└─────────────────────────────────────────────────────────┘
代码示例:权限检查实现(Java)
java
运行
@Service
public class PermissionService {
@Autowired
private UserRoleRepository userRoleRepository;
@Autowired
private RolePermissionRepository rolePermissionRepository;
@Autowired
private GroupRepository groupRepository;
// 检查用户是否有访问某个分组的权限
public boolean hasGroupPermission(Long userId, Long groupId) {
// 获取用户的所有角色
List<Long> roleIds = userRoleRepository.findRoleIdsByUserId(userId);
if (roleIds.isEmpty()) {
return false;
}
// 获取角色拥有的所有分组权限
List<Long> authorizedGroupIds = rolePermissionRepository.findGroupIdsByRoleIds(roleIds);
if (authorizedGroupIds.contains(groupId)) {
return true;
}
// 检查是否继承了父分组的权限
SysGroup group = groupRepository.findById(groupId).orElse(null);
while (group != null && group.getParentId() != 0) {
group = groupRepository.findById(group.getParentId()).orElse(null);
if (group != null && authorizedGroupIds.contains(group.getId())) {
return true;
}
}
return false;
}
// 获取用户有权限访问的所有分组ID
public List<Long> getAuthorizedGroupIds(Long userId) {
// 获取用户的所有角色
List<Long> roleIds = userRoleRepository.findRoleIdsByUserId(userId);
if (roleIds.isEmpty()) {
return Collections.emptyList();
}
// 获取角色拥有的所有分组权限
List<Long> authorizedGroupIds = rolePermissionRepository.findGroupIdsByRoleIds(roleIds);
// 获取所有子分组ID
List<Long> allAuthorizedGroupIds = new ArrayList<>(authorizedGroupIds);
for (Long groupId : authorizedGroupIds) {
List<Long> childGroupIds = getAllChildGroupIds(groupId);
allAuthorizedGroupIds.addAll(childGroupIds);
}
return allAuthorizedGroupIds.stream().distinct().collect(Collectors.toList());
}
// 递归获取所有子分组ID
private List<Long> getAllChildGroupIds(Long parentId) {
List<Long> childGroupIds = groupRepository.findIdsByParentId(parentId);
List<Long> allChildIds = new ArrayList<>(childGroupIds);
for (Long childId : childGroupIds) {
allChildIds.addAll(getAllChildGroupIds(childId));
}
return allChildIds;
}
}
3.3 素材 - 账号双向关联映射技术
素材 - 账号双向关联映射是实现内容高效生产和发布的关键,能够确保合适的素材发布到合适的账号上。
技术实现:
- 实现标题与素材的一一对应关系,支持批量导入和导出
- 建立分组级别的关联规则,某个分组的素材只能发布到对应分组的账号
- 支持多对多关联,一个素材可以关联多个账号,一个账号可以关联多个素材
- 实现关联关系的自动维护,当账号或素材移动分组时,自动更新关联关系
- 支持按分组创建 SEO 计划,针对不同分组的账号制定不同的 SEO 策略
代码示例:素材 - 账号关联实现(Java)
java
运行
@Service
public class MaterialAccountRelService {
@Autowired
private MaterialGroupRelRepository materialGroupRelRepository;
@Autowired
private AccountGroupRelRepository accountGroupRelRepository;
@Autowired
private MaterialAccountRelRepository materialAccountRelRepository;
// 根据分组自动建立素材与账号的关联
public void autoCreateRelByGroup(Long groupId) {
// 获取分组下的所有素材
List<Long> materialIds = materialGroupRelRepository.findMaterialIdsByGroupId(groupId);
// 获取分组下的所有账号
List<Long> accountIds = accountGroupRelRepository.findAccountIdsByGroupId(groupId);
// 建立关联关系
for (Long materialId : materialIds) {
for (Long accountId : accountIds) {
// 检查是否已经存在关联
if (!materialAccountRelRepository.existsByMaterialIdAndAccountId(materialId, accountId)) {
MaterialAccountRel rel = new MaterialAccountRel();
rel.setMaterialId(materialId);
rel.setAccountId(accountId);
rel.setCreateTime(new Date());
materialAccountRelRepository.save(rel);
}
}
}
}
// 获取账号关联的所有素材
public List<Long> getMaterialIdsByAccountId(Long accountId) {
// 获取账号所在的所有分组
List<Long> groupIds = accountGroupRelRepository.findGroupIdsByAccountId(accountId);
// 获取这些分组下的所有素材
List<Long> materialIds = new ArrayList<>();
for (Long groupId : groupIds) {
List<Long> groupMaterialIds = materialGroupRelRepository.findMaterialIdsByGroupId(groupId);
materialIds.addAll(groupMaterialIds);
}
// 加上直接关联的素材
List<Long> directMaterialIds = materialAccountRelRepository.findMaterialIdsByAccountId(accountId);
materialIds.addAll(directMaterialIds);
return materialIds.stream().distinct().collect(Collectors.toList());
}
// 获取素材关联的所有账号
public List<Long> getAccountIdsByMaterialId(Long materialId) {
// 获取素材所在的所有分组
List<Long> groupIds = materialGroupRelRepository.findGroupIdsByMaterialId(materialId);
// 获取这些分组下的所有账号
List<Long> accountIds = new ArrayList<>();
for (Long groupId : groupIds) {
List<Long> groupAccountIds = accountGroupRelRepository.findAccountIdsByGroupId(groupId);
accountIds.addAll(groupAccountIds);
}
// 加上直接关联的账号
List<Long> directAccountIds = materialAccountRelRepository.findAccountIdsByMaterialId(materialId);
accountIds.addAll(directAccountIds);
return accountIds.stream().distinct().collect(Collectors.toList());
}
}
3.4 分布式批量操作引擎
分布式批量操作引擎是提升矩阵运营效率的核心,能够支持针对成百上千个账号和素材的批量操作。
技术实现:
- 采用任务分片技术,将大任务拆分为多个小任务,分发到多个节点并行处理
- 实现任务幂等性保证,确保任务只会被执行一次
- 提供任务进度实时监控功能,让用户了解操作进度
- 支持任务暂停、恢复和取消操作
- 实现失败重试和错误处理机制,确保任务的可靠性
代码示例:批量发布任务实现(Java)
java
运行
@Service
public class BatchPublishService {
@Autowired
private TaskRepository taskRepository;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
// 创建批量发布任务
public Long createBatchPublishTask(List<Long> accountIds, List<Long> materialIds, Date publishTime) {
// 创建任务记录
BatchTask task = new BatchTask();
task.setType("publish");
task.setTotalCount(accountIds.size() * materialIds.size());
task.setCompletedCount(0);
task.setFailedCount(0);
task.setStatus(TaskStatus.PENDING);
task.setCreateTime(new Date());
taskRepository.save(task);
// 拆分任务为多个分片
int shardSize = 10; // 每个分片处理10个账号
for (int i = 0; i < accountIds.size(); i += shardSize) {
int end = Math.min(i + shardSize, accountIds.size());
List<Long> shardAccountIds = accountIds.subList(i, end);
// 构建任务分片消息
Map<String, Object> shardData = new HashMap<>();
shardData.put("taskId", task.getId());
shardData.put("accountIds", shardAccountIds);
shardData.put("materialIds", materialIds);
shardData.put("publishTime", publishTime);
// 发送到Kafka
kafkaTemplate.send("batch-publish-topic", JSON.toJSONString(shardData));
}
return task.getId();
}
// 处理任务分片
@KafkaListener(topics = "batch-publish-topic")
public void processPublishShard(String message) {
Map<String, Object> shardData = JSON.parseObject(message, Map.class);
Long taskId = Long.valueOf(shardData.get("taskId").toString());
List<Long> accountIds = (List<Long>) shardData.get("accountIds");
List<Long> materialIds = (List<Long>) shardData.get("materialIds");
Date publishTime = new Date(Long.valueOf(shardData.get("publishTime").toString()));
int completed = 0;
int failed = 0;
for (Long accountId : accountIds) {
for (Long materialId : materialIds) {
try {
// 执行发布操作
publishService.publish(accountId, materialId, publishTime);
completed++;
} catch (Exception e) {
log.error("发布失败: accountId={}, materialId={}", accountId, materialId, e);
failed++;
}
}
}
// 更新任务进度
updateTaskProgress(taskId, completed, failed);
}
// 更新任务进度
private void updateTaskProgress(Long taskId, int completed, int failed) {
BatchTask task = taskRepository.findById(taskId).orElse(null);
if (task == null) {
return;
}
task.setCompletedCount(task.getCompletedCount() + completed);
task.setFailedCount(task.getFailedCount() + failed);
// 检查任务是否完成
if (task.getCompletedCount() + task.getFailedCount() == task.getTotalCount()) {
task.setStatus(task.getFailedCount() == 0 ? TaskStatus.COMPLETED : TaskStatus.PARTIAL_FAILED);
task.setEndTime(new Date());
}
taskRepository.save(task);
}
}
3.5 跨分组数据隔离与共享
跨分组数据隔离与共享机制在保障数据安全的同时,提高了资源的复用率。
技术实现:
- 不同分组的数据默认相互隔离,用户只能看到自己权限范围内的数据
- 支持跨分组资源共享,可以将某个分组的素材共享给其他分组
- 实现共享申请与审批流程,保障资源共享的安全性
- 提供共享资源的版本控制,确保不同分组使用的是正确的版本
- 支持共享权限的精细化控制,可以设置查看、编辑、下载等不同权限
四、典型应用场景实现
4.1 连锁企业区域分组管理
对于拥有多家门店的连锁企业,可以按区域进行分组管理:
- 创建 "华东区"、"华南区"、"华北区" 等大区分组
- 在每个大区下创建城市分组,如 "上海"、"杭州"、"南京" 等
- 在每个城市下创建门店分组,如 "上海南京路店"、"上海徐家汇店" 等
- 将每个门店的账号和素材分配到对应的门店分组
- 为每个门店的运营人员分配对应门店分组的权限
- 总部运营人员拥有所有分组的权限,可以进行统一管理和数据统计
4.2 多品类企业品类分组管理
对于经营多个品类产品的企业,可以按品类进行分组管理:
- 创建 "美妆"、"护肤"、"彩妆"、"香水" 等品类分组
- 将对应品类的账号和素材分配到相应的分组
- 为每个品类的运营团队分配对应品类分组的权限
- 针对不同品类制定不同的内容策略和 SEO 计划
- 按品类维度进行数据统计和效果分析
4.3 代理模式客户分组管理
对于采用代理模式的企业,可以按客户进行分组管理:
- 为每个代理客户创建一个独立的分组
- 将客户的账号和素材分配到对应的客户分组
- 为客户分配对应分组的权限,客户只能管理自己的账号和素材
- 企业运营人员拥有所有客户分组的权限,可以进行统一管理和支持
- 按客户维度进行数据统计和业绩考核
4.4 团队协作角色分组管理
对于内部团队协作,可以按角色进行分组管理:
- 创建 "内容组"、"运营组"、"客服组"、"数据组" 等角色分组
- 为不同角色分配不同的权限,如内容组负责素材管理,运营组负责账号发布
- 实现跨角色的协同工作,如内容组制作的素材自动同步到运营组
- 建立完整的操作审计体系,所有操作都有记录可查
五、系统性能与安全保障
5.1 高并发批量操作优化
在大规模批量操作场景下,通过以下优化措施保障系统性能:
- 任务分片与并行处理:将大任务拆分为多个小任务,分发到多个节点并行处理
- 异步化处理:所有批量操作都采用异步方式执行,不阻塞用户请求
- 数据库优化:使用批量插入和更新语句,减少数据库交互次数
- 缓存优化:缓存常用的分组信息和权限信息,减少数据库访问
- 限流保护:对批量操作进行限流,避免系统过载
5.2 数据安全与权限保障
分组协同管理系统涉及企业的核心营销数据,安全与权限保障至关重要:
- 数据隔离:不同分组的数据严格隔离,防止数据泄露
- 精细化权限控制:实现功能、数据、操作三个维度的精细化权限控制
- 操作审计:记录所有用户的操作日志,包括操作人、操作时间、操作内容、操作结果等
- 敏感操作二次验证:对删除、批量发布等敏感操作进行二次验证
- 数据备份:定期备份分组信息和关联关系,防止数据丢失
六、实际应用效果
行业典型实践的分组协同管理系统在实际应用中取得了显著的效果:
- 矩阵账号管理效率提升 200% 以上,从原来的混乱无序变为清晰有序
- 操作错误率降低 90%,基本杜绝了发错账号、用错素材的情况
- 素材复用率提升 50%,减少了重复制作和资源浪费
- 批量操作效率提升 10 倍,原本需要一天完成的工作现在只需要几小时
- 数据统计分析效率提升 300%,能够快速按业务维度获取运营数据
七、未来技术演进方向
展望未来,分组协同管理技术将朝着以下方向演进:
- 智能分组推荐:利用 AI 技术分析账号和素材的特征,自动推荐合适的分组
- AI 自动素材分配:根据账号的定位和历史表现,自动将合适的素材分配给对应的账号
- 跨组织分组协作:支持企业与合作伙伴之间的跨组织分组协作,实现资源共享和协同运营
- 分组级数据洞察:提供更深入的分组级数据洞察和优化建议,帮助企业提升运营效果
- 低代码分组配置:提供更加灵活的低代码分组配置能力,满足企业个性化的管理需求
八、总结
本文从工程实践角度,深入拆解了 AI 原生营销矩阵系统的账号与素材分组协同管理技术,详细讲解了多维度分组模型、权限继承机制、素材 - 账号双向关联、分布式批量操作引擎、跨分组数据隔离与共享等核心技术的实现细节,并分享了典型应用场景和最佳实践。
分组协同管理作为企业级营销矩阵规模化运营的核心能力,通过将企业的组织架构映射到系统中,实现了账号、素材、人员、数据的一体化管理,有效解决了传统管理方式存在的管理混乱、权限不清、资源复用困难等问题。在未来,随着 AI 技术的不断发展,分组协同管理系统将变得更加智能化和自动化,成为企业数字化营销的核心基础设施。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)