ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Memos 自托管笔记系统的领域语言:Username、Space 与 Tag 术语规范详解

Memos 自托管笔记系统的领域语言:Username、Space 与 Tag 术语规范详解 Memos 自托管笔记系统的领域语言Username、Space 与 Tag 术语规范详解【免费下载链接】memosOpen-source, self-hosted note-taking tool built for quick capture. Markdown-native, lightweight, and fully yours.项目地址: https://gitcode.com/GitHub_Trending/me/memos本篇基于 Memos 仓库中的 领域术语表 展开系统讲解其三大核心领域的术语定义——用户与用户名Username、协作空间Space与标签Tag——并结合已接受的 ADR 决策记录与 Go 源码实现说明每个术语背后的格式规则、相等性语义与派生关系。读完本文你可以准确理解mention的识别边界、Space UID 的分配格式、标签层级展开与计数逻辑并能在修改相关功能时定位到对应的验证函数与数据结构。为什么 Memos 需要一份领域术语表Memos 的设计文档docs/目录中术语被分为两层管理产品与领域语言由 术语表 统一定义覆盖用户、空间、标签三大领域中会出现在产品行为、API 字段与设计文档里的概念协议、Unicode 与解析器专属术语则保留在使用它们的 ADR 中例如 ADR 0001标签语法与识别 中的 literal-source run、ApostropheJoiner以及 ADR 0002用户名格式与引用 中的规范化文法。这种划分意味着术语表回答这个概念是什么、不是什么ADR 回答识别规则具体如何执行而仓库中的源码校验函数、解析器、protobuf 定义则是两者的可执行落地。下面按术语表的原始脉络分三组逐项展开。一、用户与用户名User稳定身份是内部 ID不是用户名术语表第一条定义即确立了整个用户模型的基石User 是一个持久的 Memos 账户其稳定身份是内部 user ID而非可变的 username 或显示名。这意味着用户名可以重命名、可以变更但所有持久的关系memo 归属、反应、通知目标都必须绑定到 user ID 上。Username 与 Writable username一个格式三条写路径Username用户名是用户自选的、大小写敏感的公共账户标识用于认证、用户资源名、查询与引用。其核心语义可以概括为四条大小写保留拼写不做隐式修剪或归一化相等性是精确的 ASCII 字节相等——Alice与alice是两个不同的用户名与显示名和内部 ID 严格区分用户名的拼写不授予任何角色也不建立可信属性可写格式writable username适用于账户创建、重命名和自动供应provisioning三条路径规则为长度为 136 个 ASCII 字母、数字或连字符首尾必须是 ASCII 字母或数字内部连字符可以连续重复无额外限制纯数字拼写没有特殊含义123也是合法的用户名国际化的人类可读命名属于显示名display name的职责。对应的规范化文法来自 ADR 0002Username : Alphanumeric | Alphanumeric UsernameCharacter{0,34} Alphanumeric UsernameCharacter : Alphanumeric | - Alphanumeric : ASCII letter | ASCII digit上限取 36 的用意是让每个 UUID 值都有一个能装下的规范化文本表示同时保证公共 URL 与引用 token 保持有界。仓库中的实际实现是 IsValidUsername// MaxUsernameLength is the maximum number of ASCII characters in a writable username. const MaxUsernameLength 36 // IsValidUsername reports whether username satisfies the writable username format. func IsValidUsername(username string) bool { if len(username) 0 || len(username) MaxUsernameLength || !isASCIIAlphanumeric(username[0]) || !isASCIIAlphanumeric(username[len(username)-1]) { return false } for i : 0; i len(username); i { if !IsUsernameCharacter(username[i]) { return false } } return true }实现与文法逐条对应先做长度与首尾字符检查再逐字节验证内部字符只属于字母、数字或-。行为验证见 username_test.go 与前端镜像测试 username.test.ts。ADR 0002 给出的一组示例边界值得保留值是否可写原因alice、Alice-2、1alice是仅字母 / 大小写保留、内部连字符 / 数字可开头a--b、123、123-456是内部连字符无重复限制 / 纯数字合法00000000-0000-0000-0000-000000000000是规范 UUID 恰好 36 字符-alice、alice-否连字符不能位于首尾alice_smith、aliceexample.com否_与邮箱语法在格式之外álîçé、张三否可写格式仅限 ASCII此外没有保留拼写admin、root、memos等都是普通用户名按同样的精确大小写唯一性规则可用。Legacy username只读、可寻址、不可新写Legacy username遗留用户名指不满足可写格式、但为兼容性仍可读、可寻址的已存储用户名——ADR 0002 的上下文提到存量安装中可能存在邮箱地址或含下划线的旧用户名。它们的边界是不被新的写入校验接受也不会被加入新的引用语法所有查询路径必须保留其存储拼写。数据库层面的大小写敏感相等性由于相等是精确 ASCII 字节相等ADR 0002 进一步要求每个受支持的存储后端的username列都使用显式的大小写敏感二进制排序规则MySQL 上用utf8mb4_binPostgreSQL 上用CSQLite 上用BINARY。这样精确字节唯一性就成为 schema 属性而不是对各数据库默认排序规则的假设——任何把Alice与alice视为相等的 collation 或查询都不符合该决策。Username reference、Mention candidate 与 Resolved mention这三个术语构成 提及的完整识别—解析链条Username reference用户名引用以精确、大小写敏感的方式命名一个用户名的源文本使 Memos 可以尝试将其解析为 user ID。注意源文本拼写本身不是持久的用户绑定——跨用户名重命名与复用的稳定性需要额外的持久化身份而当前规范未定义它。Mention candidate提及候选由 ASCII后跟一个完整可写用户名形成的合格 markdown 源片段。识别时应用当前用户名引用规范中的边界与 markdown 上下文规则。关键设计是用户名格式本身定义了提及边界——用户名允许的字母、数字、连字符若出现在右侧会阻断提及识别若出现在候选左侧则被视为完整候选的一部分。其余字符不享受特殊边界处理。Resolved mention已解析提及其精确用户名在消费方操作既有的账户状态与可见性策略下解析到某个用户的提及候选。针对用户的效果作用于解析出的 user ID而非未解析的源文本。ADR 0002 规定的候选识别规则可归纳为必须是合格字面源片段中的 U0040除文档开头外紧邻的前一个源字符不得是UsernameCharacter即前置的字母、数字或连字符都会阻断识别词法器消费之后完整的字母/数字/连字符连续段然后对整段做可写用户名校验——不会把一个非法连续段截短成合法前缀GFM 邮箱识别优先被识别为邮箱的子串不是提及。由此产生的典型边界行为源文本提及候选alice、hi, alice.、中文alice、foo_alicealiceAlice-2、123Alice-2、123helloalice、foo-alice、-alice、alice-无alice_smith、alicebobalice_与是普通边界aliceexample.com无邮箱优先后跟 37 个合法字符无超出 36 字符上限在 markdown 上下文层面提及识别运行于 GFM 解析之后段落、标题、引用、列表、表格文本、强调等暴露普通文本而代码片段、代码块、已解析的链接与图片、autolink、GFM 邮箱、原始 HTML 与数学公式都是**不透明opaque**的。解析、阅读渲染、元数据提取、编辑器装饰与未来的提及补全必须使用同一套词法与上下文规则。二、SpacesSpace 与 Space IDSpace空间是实例范围内的协作边界约束已接受成员与 memo 的放置。术语表特别强调它不是租户tenant、文件夹或应用级授权角色——这一界定防止把 Space 误当作多租户隔离或权限模型来使用。Space ID是 Space 的稳定内部身份与公共的 Space UID 和可变的 Space title 三者严格区分。三者对照如下术语可变性用途Space ID不变内部稳定身份Space UID不变创建时分配实例内唯一公共标识可用户自定义或自动生成Space title可变、不唯一显示标签Space 资源名与 UID 格式Space 资源名是 Space 在 API 中的身份形式为spaces/{space UID}。ADR 0003Space UID 分配与格式 决定了 UID 的分配方式与文法第一方客户端为每个新 Space 生成规范小写 UUID v4通过CreateSpaceRequest.space_id发送该值可以在创建前暴露给用户并允许替换为自定义 UID同一创建交互的重试复用同一生成值API 字段保持可选以兼容旧客户端为空时服务器生成规范小写 UUID v4提供的值使用与 username 共享的公共资源文法SpaceUID : Alphanumeric | Alphanumeric UIDCharacter{0,34} Alphanumeric UIDCharacter : Alphanumeric | - Alphanumeric : ASCII letter | ASCII digit即 UID 为 136 字符内部连字符可连续、允许大写字母、纯数字也合法拼写保留。这一文法在仓库中的实现是 UIDMatcherUIDMatcher regexp.MustCompile(^a-zA-Z0-9?$)与用户名文法完全同构——这正是 ADR 0003 决策驱动中复用已建立的公共资源 UID 文法而不是引入 Space 专属 slug 格式的落地。ADR 同时明确旧客户端省略该字段时由服务器回退生成存量短 UID 保持有效且不做数据迁移UID 冲突由既有的实例内唯一性约束拒绝。多空间设计背景可参见 multi-spaces 设计文档各存储后端的实现分别在 sqlite/space.go、mysql/space.go 与 postgres/space.go。三、Tags标签是 Memos markdown 语言的一部分而不只是编辑器装饰一次标签出现会影响渲染内容、memo 载荷中派生的 tags、API 响应、标签计数、过滤、元数据与编辑器补全。术语表对标签域的定义可组织为识别 → 取值 → 相等 → 集合 → 计数的链条。识别层Tag、Tag introducer 与 Tag occurrenceTag标签是 memo 标签集合中的一个分类值由一个或多个标签出现派生形式可以是直接值direct tag value也可以是隐含祖先implied ancestor。标签不是持久实体不能被独立创建或重命名——改一个标签的拼写意味着编辑产生它的 memo 源文本。Tag introducer标签引导符是 ASCII#U0023且不构成已匹配的完全限定 emoji 序列的一部分。视觉上相似的全角井号UFF03与小号井号UFE5F不是引导符只是普通文本。Tag occurrence标签出现是由引导符与其消费的源拼写组成的合格源片段拼写发出一个 tag identifier即直接标签值。识别规则包括排除的 markdown 上下文由当前标签语法配置ADR 0001定义。Eligible tag text合格标签文本是不含任何 markdown 语法边界的中断源范围按 GFM 0.29-gfm 归类为文本内容或被 Memos markdown 扩展显式暴露为普通文本。扩展节点默认不透明除非其定义显式选择参与识别——这保证了未来自定义扩展不会意外泄漏出标签。取值层Source spelling 与 Direct tag valueSource spelling源拼写是#引导符之后被消费的精确源子串包含两个词法器会忽略的部分已匹配的完全限定 emoji 序列之外的默认可忽略码点default-ignorable code points以及每个段segment起始符之前被忽略的前置组合标记。行内渲染与保源操作用这个拼写。Direct tag value直接标签值是一个标签出现经过层级展开之前发出的 identifier。与源拼写的差异在于上述两类被忽略的码点从源拼写中被消费但从值中省略。例如#book/fiction产生的直接值就是book/fiction。层级层Tag segment、Implied ancestor 与 Tag identifierTag segment标签段是层级标签 identifier 中的非空组件。/分隔各段且只在其后跟随另一个非空段时才被消费前导斜杠不产生标签尾部或重复的斜杠在该斜杠处终结 identifier。-、、是普通段单元可出现在任意位置、可重复、可以构成整个段。默认可忽略码点与被忽略的前置组合标记本身不能让一个段变为非空。Implied ancestor tag隐含祖先标签由直接值按斜杠切分的前缀派生直接值book/fiction/history隐含祖先标签book与book/fiction。Tag identifier标签标识符是从标签源拼写发出的非空 Unicode 码点序列排除引导符与所有被忽略的源码点。相等性层Display value 与 Comparison keyDisplay value显示值是作为派生标签标签呈现的直接值或隐含值。Memos 不对发出的码点做归一化或 case-fold但被忽略的默认可忽略码点与被忽略的前置组合标记不属于显示值。Comparison key比较键是用于去重、计数、过滤、导航与精确元数据匹配的值与显示值完全相同。两个标签只有当发出的 Unicode 码点序列相同时才比较相等大小写不同、规范化等价或兼容性等价的拼写都是不同标签。反过来仅因被忽略码点而不同的源拼写比较为相等。另外精确值过滤与元数据查询的输入已经是标签值按原样比较不会被重新词法分析。ADR 0001 给出的相等性示例很能说明问题#Work与#work、#与#A、#straße与#STRASSE、#OBrienU0027与#OBrienU2019各是两对不同的标签而#AB、#A‍B含 ZWJ、#A‌B含 ZWNJ三者发出同一比较键AB。集合层Memo tag set、Tag metadata rule 与 Tag countMemo tag set是一个 memo 的直接标签值与其隐含祖先的并集通过Memo.tags字段暴露。它是可从 memo 的 markdown 重建的派生索引不是标签的权威来源同一 memo 中精确相等的值只出现一次即使它既被直接产生又作为祖先产生。对应的数据定义在 memo.proto 中repeated string tags 3;即标签只是 memo 上的一组字符串投影。Tag metadata rule标签元数据规则是用户配置用于选择标签值并提供展示或行为元数据如颜色、内容模糊。一条规则可以匹配多个值但不创建、不拥有、不重命名标签。Tag count标签计数是包含精确相等直接值或隐含值的 memo 标签集合的数量而不是文本出现次数。一个只含#book/fiction的 memo对book与book/fiction的计数各贡献 1。源码落地标签识别在哪里执行Go 端的词法实现在 internal/markdown/parser/tag.go 中。TagMatch结构保存一次识别的字节偏移范围与发出的直接值type TagMatch struct { // Start is the byte offset of the introducer within the source run. Start int // End is the exclusive byte offset of the recognized source spelling. End int // Value is the emitted direct tag value without the introducer. Value []byte }这正体现了术语表中识别源 span 含引导符发出值不含引导符的区分。识别基于固定版本的 Unicode 17.0 属性表XID_Continue、Default_Ignorable_Code_Point等由 tagdata 生成器 离线生成 tag_unicode_tables.goemoji 匹配则使用 Emoji 17.0 数据中的fully-qualified条目做最长序列匹配见matchFullyQualifiedEmoji而不是简单码点字符类——这就是术语表反复强调已匹配的完全限定 emoji 序列的原因。词法行为由 tag_test.go 与前端镜像测试如 tag-grammar.test.ts、tag-unicode-parity.test.ts共同锁定前端实现入口在 web/src/lib/tag.ts 与 remark 扩展 tag.go 对应的 extensions/tag.go。ADR 0001 中的两个关键语义值得单独点出因为它们直接决定计数与过滤的正确性极大前缀扫描#book/产生book尾部斜杠不消费#book//fiction产生book重复斜杠在第一个斜杠前终结#/book不产生标签——即保留已消费的有效前缀后续非法字符不使其失效。markdown 上下文过滤**#urgent**中urgent是标签格式化文本仍合格而#35;tag、\#tag、内联代码#tag、链接hello#tag、数学节点$#tag$中都不产生标签——转义、字符引用与不透明节点是硬边界候选不能跨越它们。术语间的一致性设计通读术语表后可以提炼出贯穿三个领域的统一原则这也是把术语表与 ADR、源码放在一起读的价值所在稳定身份与公共标识分离User 的 user ID、Space 的 Space ID 是稳定身份username、Space UID、title 是公共/显示层。三者都不可互换。公共标识共用一套文法username 与 Space UID 使用同构的136 字符、字母数字首尾、内部连字符文法对应源码中 IsValidUsername 与 UIDMatcher 两处实现避免各写路径各自发明 token 语法。派生值永远从源文本重建Memo.tags是派生投影标签不是实体username reference 的源拼写不是持久绑定。凡是从源派生的东西其正确性以 markdown/源文本为准存储副本只是可重建索引。相等性处处精确用户名是精确 ASCII 字节相等并落到数据库二进制 collation标签是精确 Unicode 码点序列相等不做 case fold 或规范化。Memos 刻意不引入任何 locale 敏感的模糊相等让两个拼写是否相同成为确定性的机器判定。如果你要扩展这些领域例如给编辑器加提及补全、给 Space 加新的标识展示位、或调整标签元数据行为建议的路径是先对照 docs/glossary.md 确认术语边界再查对应的 ADR 决策记录ADR 0001、ADR 0002、ADR 0003最后到 internal/base、internal/markdown 与 store/db 中定位既有实现与测试保证新行为与精确相等、源文为准、派生可重建这三条原则一致。【免费下载链接】memosOpen-source, self-hosted note-taking tool built for quick capture. Markdown-native, lightweight, and fully yours.项目地址: https://gitcode.com/GitHub_Trending/me/memos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表