### 空间 {{% added-in v="1.2" %}} 空间常用于将主题相似的房间进行分组(例如公共的“Official matrix.org rooms”空间或个人的“Work stuff”空间),是一种用于组织房间的方式,同时自身也被表示为房间。 空间通过 [`m.space` 房间类型](#types) 定义,因此被称为“空间房间(space-room)”。空间的名称、主题、头像、别名等,均通过空间房间内已有的相关状态事件来定义。 在空间房间内发送普通的 [`m.room.message`](#mroommessage) 事件是不推荐的——客户端通常并不预期会有方法渲染该房间的时间线。因此,空间房间应当通过设置 [`m.room.power_levels`](#mroompower_levels) 中的 `events_default` 为足够高的数值来禁止普通事件的发布。在默认的权限层级结构中,该值应为 `100`。客户端还可进一步不对空间房间计入通知数。 空间成员资格通过现有的房间管理机制定义和控制:即 [`m.room.member`](#mroommember)、[`m.room.history_visibility`](#mroomhistory_visibility) 和 [`m.room.join_rules`](#mroomjoin_rules)。建议公共空间与公共房间采用类似设置:`world_readable` 的历史可见性、已发布的规范别名以及适当的公共加入规则。邀请(包括第三方邀请)同样适用于空间房间。 常规房间的其他特性也同样适用于空间,如可设置任意状态事件、存储房间账户数据等。空间本质上是具有额外功能的房间。 #### 管理空间内包含的房间/空间 空间组成了一个房间层级体系,客户端可借此将房间列表结构化为树状视图。父子关系有两种定义方式:在空间房间中通过 [`m.space.child`](#mspacechild) 状态事件,或在子房间中通过 [`m.space.parent`](#mspaceparent) 状态事件。 多数情况下,应同时定义子房间与父房间的关系,以便于发现空间及其内容。仅使用 `m.space.child` 时,空间实质上就像一个由空间管理者精心挑选的房间列表,而房间本身可能并不知晓被包含其中。仅使用 `m.space.parent` 时,房间则会被“秘密”添加到空间中,而不会被空间直接宣传。 {{% boxes/warning %}} 鉴于空间本身就是房间,因此可以在空间中嵌套空间,也存在创建环路(循环)的可能。尽管明确禁止创建循环,实际实现时仍可能遇到这种情况,必须注意避免无限循环。 客户端及服务器还需警惕树结构过长,以免带来性能问题。 {{% /boxes/warning %}} ##### `m.space.child` 关系 采用此方法时,状态事件发送至作为父房间的空间房间,事件的 `state_key` 为子房间的ID。 例如,实现以下结构: ``` #space:example.org #general:example.org (!abcdefg:example.org) !private:example.org ``` 则 `#space:example.org` 的状态为: *为简明起见,省略无关字段。* ```json { "type": "m.space.child", "state_key": "!abcdefg:example.org", "content": { "via": ["example.org"] } } ``` ```json { "type": "m.space.child", "state_key": "!private:example.org", "content": { "via": ["example.org"] } } ``` 子房间本身无需任何状态事件(当然也可以存在)。如此,用户可以无需房间版主/管理员明确授权,定义个人/私有空间组织自己的房间。 通过在相关状态事件的 `content` 中省略 `via` 键(如通过撤回或清空 `content`),可将子房间从空间中移除。 {{% event event="m.space.child" %}} ###### 空间内子项排序 当客户端展示空间的子项时,应按照以下算法排序。在部分场景(如传统的左侧房间列表),客户端可能会覆盖排序规则以提升用户体验。但理论上的空间摘要视图会显示有序的子项。 对于空间的所有子项,先将带有有效 `order` 键的子项按照 Unicode 码点字典序排序,使得 `\x20`(空格)在 `\x7E`(`~`)之前。再将剩余未定义 `order` 的子项按其 `m.space.child` 事件的 `origin_server_ts` 时间戳升序排列,置于前者之后。 若 `order` 值相同,则按事件时间戳排序。若时间戳也相同,则按照房间ID(即 state key)升序字典序排列。 注意此处对 ASCII 空格的精确用法,以下为一组空间子项的合理排序示例: *为简明起见,省略无关字段。* ```json [ { "type": "m.space.child", "state_key": "!b:example.org", "origin_server_ts": 1640341000000, "content": { "order": " ", "via": ["example.org"] } }, { "type": "m.space.child", "state_key": "!a:example.org", "origin_server_ts": 1640141000000, "content": { "order": "aaaa", "via": ["example.org"] } }, { "type": "m.space.child", "state_key": "!c:example.org", "origin_server_ts": 1640841000000, "content": { "order": "first", "via": ["example.org"] } }, { "type": "m.space.child", "state_key": "!e:example.org", "origin_server_ts": 1640641000000, "content": { "via": ["example.org"] } }, { "type": "m.space.child", "state_key": "!d:example.org", "origin_server_ts": 1640741000000, "content": { "via": ["example.org"] } } ] ``` 1. `!b:example.org` 排在最前,因为 `\x20` 字典序在 `aaaa` 之前。 2. `!a:example.org` 紧随其后,因为 `aaaa` 字典序在 `first` 之前。 3. `!c:example.org` 接下来,因为 `first` 是最后一个 `order` 值。 4. `!e:example.org` 其后,因为其事件时间戳最小。 5. `!d:example.org` 最后,因为其事件时间戳最大。 ##### `m.space.parent` 关系 房间还可以通过在自身状态中添加父房间事件来声明属于某个空间。类似空间中的子事件,父事件的 `state_key` 是父空间的房间ID,`content` 中的 `via` 列表用于说明链接是否有效,以及可通过哪些服务器加入。 为避免房间伪称属于某空间,`m.space.parent` 事件在下列任一条件满足时才应被接受: * 在设定为父空间的房间中可找到对应的 `m.space.child` 事件。 * `m.space.parent` 事件的发送者在该父空间拥有足够权限可发送 `m.space.child` 状态事件(无需确有对应子事件存在)。 {{% boxes/note %}} 如客户端尚未加入父空间,可能需要窥探父空间房间状态。若客户端无法窥探,则应认为链接无效。 {{% /boxes/note %}} {{% boxes/note %}} 第二个条件的后果是:若房间管理员在父空间内被降权、退出或被移除父空间,则之前合法的 `m.space.parent` 事件可能变为无效。 {{% /boxes/note %}} `m.space.parent` 事件的 `content` 可包含布尔值 `canonical`,表示该父空间为该房间的主空间。例如可用于让客户端通过窥探该空间发现其他相关房间并向用户推荐。只能有一个主(canonical)父空间,尽管这一点没有强制约束。若有冲突,采用 Unicode 码点升序排序的最小房间ID进行决议。 {{% event event="m.space.parent" %}} #### 在空间中发现房间 客户端常需帮助用户探索某空间包含哪些房间/空间。可通过在客户端中遍历该空间的 [`m.space.child`](#mspacechild) 状态事件并窥探房间以获取名称等信息,然而这种方式在大多数场景下并不实用。 为此,提供了一个层级API以深度优先方式遍历空间树并发现带美观细节的房间信息。 [`GET /hierarchy`](#get_matrixclientv1roomsroomidhierarchy) API 按深度优先方式工作:遇到子项为空间时将递归至该空间,然后返回非空间子房间。 {{% boxes/warning %}} 循环虽被禁止,但仍有可能出现。服务器应优雅地中断循环。 此外,某个子房间(例如作为孙子房间)可能多次出现在响应中。 {{% /boxes/warning %}} {{% http-api spec="client-server" api="space_hierarchy" %}} ##### 服务器行为 当服务器无法获取子房间状态时,可通过联邦接口 [`GET /hierarchy`](/server-server-api/#get_matrixfederationv1hierarchyroomid) 请求所需信息。该端点响应应在一段时间内做缓存。响应还可包含请求用户已加入或本地服务器已知的房间信息——本地数据应优先于远端服务器数据。 请注意,返回客户端的响应基于用户上下文。建议服务器对数据做短暂缓存,但仍须进行权限校验以确保响应对于该用户的准确性。