This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch release_0.17.0 in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit 0c9f74409b00f9a4067585ac235d69996d1deb47 Author: 慕白 <[email protected]> AuthorDate: Sun Apr 19 23:24:21 2026 +0800 Complete zh docs and blog for 0.17 --- .../2026-04-19-fory_0_17_0_release.md | 52 +++++ .../current/guide/csharp/index.md | 22 +-- .../current/guide/dart/_category_.json | 6 + .../current/guide/dart/basic-serialization.md | 144 ++++++++++++++ .../current/guide/dart/code-generation.md | 113 +++++++++++ .../current/guide/dart/configuration.md | 121 ++++++++++++ .../current/guide/dart/cross-language.md | 193 ++++++++++++++++++ .../current/guide/dart/custom-serializers.md | 139 +++++++++++++ .../current/guide/dart/field-configuration.md | 127 ++++++++++++ .../current/guide/dart/index.md | 141 +++++++++++++ .../current/guide/dart/schema-evolution.md | 92 +++++++++ .../current/guide/dart/supported-types.md | 102 ++++++++++ .../current/guide/dart/troubleshooting.md | 103 ++++++++++ .../current/guide/dart/type-registration.md | 98 +++++++++ .../current/guide/java/enum-configuration.md | 104 ++++++++++ .../current/guide/java/virtual-threads.md | 114 +++++++++++ .../current/guide/javascript/_category_.json | 6 + .../guide/javascript/basic-serialization.md | 219 +++++++++++++++++++++ .../current/guide/javascript/cross-language.md | 128 ++++++++++++ .../current/guide/javascript/index.md | 141 +++++++++++++ .../current/guide/javascript/references.md | 111 +++++++++++ .../current/guide/javascript/schema-evolution.md | 99 ++++++++++ .../current/guide/javascript/supported-types.md | 177 +++++++++++++++++ .../current/guide/javascript/troubleshooting.md | 105 ++++++++++ .../current/guide/javascript/type-registration.md | 200 +++++++++++++++++++ 25 files changed, 2846 insertions(+), 11 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-blog/2026-04-19-fory_0_17_0_release.md b/i18n/zh-CN/docusaurus-plugin-content-blog/2026-04-19-fory_0_17_0_release.md index 7a709c9c0..a1752fa04 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-blog/2026-04-19-fory_0_17_0_release.md +++ b/i18n/zh-CN/docusaurus-plugin-content-blog/2026-04-19-fory_0_17_0_release.md @@ -228,3 +228,55 @@ void main() { * fix(java): 在首次使用时固定 codegen config hash,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3495 * fix(rust): 修复 cargo-fuzz 检测到的多个 panic,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3483 * fix(java): 处理 private final map 的代码生成,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3504 +* fix(compiler): 修复失效的 service 示例并补充回归覆盖,作者 @VikingDeng,见 https://github.com/apache/fory/pull/3505 +* fix(rust): 深拷贝 TypeMeta,避免并发场景中的未定义行为,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3511 +* fix(compiler): 修复 compiler 测试失败问题,并增加 compiler CI,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3516 +* fix(compiler): 允许在 FDL RPC 签名中使用带限定名的嵌套类型,作者 @VikingDeng,见 https://github.com/apache/fory/pull/3518 +* fix(java): 在复制时保留 ConcurrentSkipListSet comparator,作者 @mandrean,见 https://github.com/apache/fory/pull/3520 +* test(compiler): 为 gRPC service 支持增加 IR 校验与代码生成测试,作者 @darius024,见 https://github.com/apache/fory/pull/3528 +* fix(java): 修正 GraalVM 中依赖/嵌套 serializer 的解析,作者 @rakow,见 https://github.com/apache/fory/pull/3532 +* fix(java): 修复 objectstream serializer 的异步 JIT 与 GraalVM 支持,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3534 +* ci: 修复 C++ workflow 的 Bazel 缓存路径,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3535 +* fix(python): 修复 visit_other 中错误的调用顺序,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3542 +* fix(js): 修正 float64 array 大小计算错误,作者 @ayush00git,见 https://github.com/apache/fory/pull/3541 +* fix(java): 在构建期间配置 type checker,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3550 +* fix(java): 为排序容器自动选择子 serializer,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3552 +* fix(java): 支持在 java11+ 中跳过可选 SQL serializer,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3553 +* fix(rust): 为嵌套类型统一应用 PascalCase 命名,作者 @utafrali,见 https://github.com/apache/fory/pull/3548 +* fix(javascript): 修复不稳定的 javascript IDL 测试,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3565 +* fix(swift): 修复 swift 生成代码的编译告警,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3566 +* fix(java): 为此前遗漏的回归问题增加测试,作者 @PiotrDuz,见 https://github.com/apache/fory/pull/3573 + +## 其他改进 +* chore(java): 更新 Spotless 和 Checkstyle,以支持使用 JDK25 构建,作者 @stevenschlansker,见 https://github.com/apache/fory/pull/3476 +* chore(java): 清理 Javadoc 告警,并在引入新告警时让构建失败,作者 @stevenschlansker,见 https://github.com/apache/fory/pull/3477 +* chore: 将版本号提升到 0.16.0,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3488 +* docs: 在 readme 中增加 python benchmark 结果,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3496 +* docs: 修复 benchmark 图表,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3498 +* docs: 新增 fory code review skill,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3500 +* chore: 调整 skills 软链接,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3501 +* chore(cpp): 配置 CI,从 Markdown 中提取并执行 C++ 代码,作者 @Tyooughtul,见 https://github.com/apache/fory/pull/3381 +* docs: 改进 Java generator 中的注释,作者 @codewithtarun2005,见 https://github.com/apache/fory/pull/3507 +* docs: 重构 agents.md 以减少 token 使用,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3538 +* docs: 增加 ai-review 策略,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3545 +* chore: 在 /java/fory-test-core 中将 org.apache.logging.log4j:log4j-core 从 2.25.3 升级到 2.25.4,作者 @dependabot[bot],见 https://github.com/apache/fory/pull/3556 +* chore(javascript): 将 ref tracking 重命名为 ref,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3559 +* docs(dart): 增加 dart 文档,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3560 +* docs(javascript): 增加 javascript 文档,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3561 +* docs: 完善 dart 和 javascript 文档,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3567 +* docs(dart): 更新 dart benchmark 文档,作者 @yash-agarwa-l,见 https://github.com/apache/fory/pull/3568 +* docs: 更新 readme,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3575 + +## 新贡献者 +* @utafrali 首次贡献见 https://github.com/apache/fory/pull/3481 +* @BaldDemian 首次贡献见 https://github.com/apache/fory/pull/3490 +* @retryoos 首次贡献见 https://github.com/apache/fory/pull/3493 +* @Tyooughtul 首次贡献见 https://github.com/apache/fory/pull/3381 +* @UninspiredCarrot 首次贡献见 https://github.com/apache/fory/pull/3487 +* @VikingDeng 首次贡献见 https://github.com/apache/fory/pull/3505 +* @codewithtarun2005 首次贡献见 https://github.com/apache/fory/pull/3507 +* @darius024 首次贡献见 https://github.com/apache/fory/pull/3528 +* @rakow 首次贡献见 https://github.com/apache/fory/pull/3532 +* @PiotrDuz 首次贡献见 https://github.com/apache/fory/pull/3573 + +**完整更新日志**: https://github.com/apache/fory/compare/v0.16.0...v0.17.0 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md index 4cd2780ce..30f8b06dc 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md @@ -85,17 +85,17 @@ User decoded = fory.Deserialize<User>(payload); | 主题 | 说明 | | --------------------------------------------- | ---------------------------------- | -| [配置](configuration.md) | 构建器选项与运行时模式 | -| [基础序列化](basic-serialization.md) | 强类型和动态序列化 API | -| [类型注册](type-registration.md) | 注册用户类型和自定义序列化器 | -| [自定义序列化器](custom-serializers.md) | 实现 `Serializer<T>` | -| [字段配置](field-configuration.md) | `[Field]` 特性与整数编码选项 | -| [引用](references.md) | 共享引用与循环引用处理 | -| [Schema 演进](schema-evolution.md) | 兼容模式行为 | -| [跨语言](cross-language.md) | 互操作性指导 | -| [支持的类型](supported-types.md) | 内置类型与生成类型支持 | -| [线程安全](thread-safety.md) | `Fory` 与 `ThreadSafeFory` 的用法 | -| [故障排查](troubleshooting.md) | 常见错误与调试步骤 | +| [配置](https://fory.apache.org/docs/guide/csharp/configuration) | 构建器选项与运行时模式 | +| [基础序列化](https://fory.apache.org/docs/guide/csharp/basic-serialization) | 强类型和动态序列化 API | +| [类型注册](https://fory.apache.org/docs/guide/csharp/type-registration) | 注册用户类型和自定义序列化器 | +| [自定义序列化器](https://fory.apache.org/docs/guide/csharp/custom-serializers) | 实现 `Serializer<T>` | +| [字段配置](https://fory.apache.org/docs/guide/csharp/field-configuration) | `[Field]` 特性与整数编码选项 | +| [引用](https://fory.apache.org/docs/guide/csharp/references) | 共享引用与循环引用处理 | +| [Schema 演进](https://fory.apache.org/docs/guide/csharp/schema-evolution) | 兼容模式行为 | +| [跨语言](https://fory.apache.org/docs/guide/csharp/cross-language) | 互操作性指导 | +| [支持的类型](https://fory.apache.org/docs/guide/csharp/supported-types) | 内置类型与生成类型支持 | +| [线程安全](https://fory.apache.org/docs/guide/csharp/thread-safety) | `Fory` 与 `ThreadSafeFory` 的用法 | +| [故障排查](https://fory.apache.org/docs/guide/csharp/troubleshooting) | 常见错误与调试步骤 | ## 相关资源 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/_category_.json new file mode 100644 index 000000000..b2d06cfcd --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Dart", + "position": 9, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/basic-serialization.md new file mode 100644 index 000000000..59aaed47a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/basic-serialization.md @@ -0,0 +1,144 @@ +--- +title: 基础序列化 +sidebar_position: 2 +id: dart_basic_serialization +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页介绍如何使用 Apache Fory™ Dart 对值进行序列化和反序列化。 + +## 创建 `Fory` 实例 + +创建一个实例并复用它。每次调用都新建 `Fory` 只会浪费资源。 + +```dart +import 'package:fory/fory.dart'; + +final fory = Fory(); +``` + +## 序列化和反序列化带注解的类型 + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + Int32 age = Int32(0); +} + +void main() { + final fory = Fory(); + PersonFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', + ); + + final person = Person() + ..name = 'Ada' + ..age = Int32(36); + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize<Person>(bytes); + print(roundTrip.name); +} +``` + +`deserialize<T>` 会返回并转换为 `T` 的解码结果。如果载荷描述的类型与 `T` 不一致,就会抛出异常。 + +## Null 值 + +支持直接序列化 `null`: + +```dart +final fory = Fory(); +final bytes = fory.serialize(null); +final value = fory.deserialize<Object?>(bytes); +``` + +## 序列化集合和动态载荷 + +你可以直接序列化集合值: + +```dart +final fory = Fory(); +final bytes = fory.serialize(<Object?>[ + 'hello', + Int32(42), + true, +]); +final value = fory.deserialize<List<Object?>>(bytes); +``` + +对于异构集合,请反序列化为 `Object?`、`List<Object?>` 或 `Map<Object?, Object?>`。 + +## 引用跟踪 + +默认情况下,Fory 不会跟踪对象标识。如果同一个对象在列表中出现两次,它会被序列化两次。当数据包含共享引用或循环结构时,请启用引用跟踪。 + +对于顶层集合: + +```dart +final fory = Fory(); +final shared = String.fromCharCodes('shared'.codeUnits); +final bytes = fory.serialize(<Object?>[shared, shared], trackRef: true); +final roundTrip = fory.deserialize<List<Object?>>(bytes); +print(identical(roundTrip[0], roundTrip[1])); // true +``` + +对于生成结构体中的字段,请改用该字段上的 `@ForyField(ref: true)`。 + +## 复用缓冲区 + +如果你想避免每次调用都分配新的 `Uint8List`,可以配合显式 `Buffer` 使用 `serializeTo` 和 `deserializeFrom`: + +```dart +final fory = Fory(); +final buffer = Buffer(); + +fory.serializeTo(Int32(42), buffer); +final value = fory.deserializeFrom<Int32>(buffer); +``` + +这是性能优化手段。对大多数应用来说,默认的 `serialize` / `deserialize` 就足够了。 + +## 在序列化前注册类型 + +在序列化自定义类或枚举之前,必须先把它注册到 `Fory` 中。生成代码会让这件事变得很简单: + +```dart +PersonFory.register( + fory, + Person, + id: 100, +); +``` + +如果跳过注册,运行时会得到 `Type ... is not registered` 错误。参见 [类型注册](type-registration.md) 和 [代码生成](code-generation.md)。 + +## 相关主题 + +- [配置](configuration.md) +- [类型注册](type-registration.md) +- [字段配置](field-configuration.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/code-generation.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/code-generation.md new file mode 100644 index 000000000..53b760eb5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/code-generation.md @@ -0,0 +1,113 @@ +--- +title: 代码生成 +sidebar_position: 3 +id: dart_code_generation +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Fory 会在构建阶段为你的 Dart 类生成高性能序列化代码。你只需要给模型加注解、运行 `build_runner`,剩下的由 Fory 处理。 + +## 第一步:给模型加注解 + +为每个需要序列化的类添加 `@ForyStruct()`。同时在文件顶部加入生成的 `part` 指令。 + +```dart +import 'package:fory/fory.dart'; + +part 'models.fory.dart'; + +@ForyStruct() +class Address { + Address(); + + String city = ''; + String street = ''; +} + +@ForyStruct() +class User { + User(); + + String name = ''; + Int32 age = Int32(0); + Address address = Address(); +} +``` + +定义在同一文件中的枚举会自动包含到生成的注册代码里。 + +## 第二步:运行生成器 + +在包含 `pubspec.yaml` 的目录下运行: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +这会在源文件旁边生成一个 `.fory.dart` 文件。每当你新增或重命名带注解的类型时,都需要重新运行这个命令。 + +## 第三步:注册并使用 + +生成器会创建一个以源文件命名的命名空间,并提供 `register` 函数。请在序列化前调用它: + +```dart +final fory = Fory(); +ModelsFory.register(fory, Address, id: 1); +ModelsFory.register(fory, User, id: 2); +``` + +也可以用稳定名称代替数字 ID,这在跨语言场景中更有用: + +```dart +ModelsFory.register( + fory, + User, + namespace: 'example', + typeName: 'User', +); +``` + +关于如何在 ID 和名称之间做选择,见 [类型注册](type-registration.md)。 + +## Schema 演进:`evolving` + +`@ForyStruct()` 默认使用 `evolving: true`,这对大多数应用都是正确选择。 + +- `evolving: true`:Fory 会保存足够的元信息,因此当你之后新增或删除字段时,旧代码与新代码仍然可以交换消息。只要你的应用或服务可能存在多个版本同时运行,就应该启用它。 +- `evolving: false`:不写入额外元信息,载荷会略小一些。只有在写端和读端总是一起升级时才安全。 + +```dart +// evolving: true 是默认值,可以省略 +@ForyStruct(evolving: true) +class Event { + Event(); + + String name = ''; +} +``` + +使用 evolving 结构体时,也要在首次对外发送载荷之前通过 `@ForyField(id: ...)` 为字段分配稳定 ID,因为 Fory 会依赖这些 ID 在 Schema 变化后匹配字段。 + +## 什么时候不要使用代码生成 + +如果你无法给某个类型加注解,例如它来自一个你无法修改的第三方包,那就改用 [自定义序列化器](custom-serializers.md)。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [字段配置](field-configuration.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md new file mode 100644 index 000000000..0f53ceb69 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md @@ -0,0 +1,121 @@ +--- +title: 配置 +sidebar_position: 1 +id: dart_configuration +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页介绍 `Fory` 构造函数的可选项。 + +## 创建 `Fory` 实例 + +直接把选项传给构造函数: + +```dart +import 'package:fory/fory.dart'; + +// 默认配置,适合大多数单服务场景 +final fory = Fory(); + +// 需要 Schema 演进的跨语言服务 +final fory = Fory( + compatible: true, + maxDepth: 512, +); +``` + +每个应用创建一个实例并复用即可。按请求新建 `Fory` 没有任何收益。 + +## 选项 + +### `compatible` + +当你的服务需要处理来自另一份模型版本代码的载荷时,请设置为 `true`。例如各服务独立发布,无法保证通信双方同时升级。 + +```dart +final fory = Fory(compatible: true); +``` + +当 `compatible: true` 时: + +- 一侧新增或删除字段不会破坏另一侧。 +- 各端仍然必须使用相同的 `namespace` + `typeName`,或者相同的数字 `id` 来标识类型。 + +当 `compatible: false`(默认)时: + +- 双方必须拥有完全相同的 Schema。这样会略快一些,适合仅有 Dart 服务或始终一起升级的场景。 + +### `checkStructVersion` + +仅在 `compatible: false` 时相关。当它为 `true` 时,Fory 会校验载荷中的 Schema 版本是否与接收端已知版本一致,从而在运行时尽早发现误用的 Schema。 + +```dart +final fory = Fory( + compatible: false, + checkStructVersion: true, // default +); +``` + +当 `compatible: true` 时,这个选项不起作用。 + +### `maxDepth` + +限制对象图的最大嵌套深度。如果你的数据确实有很深的树形结构,可以增大它;如果你想快速拒绝异常深的载荷,可以减小它。 + +```dart +final fory = Fory(maxDepth: 128); +``` + +### `maxCollectionSize` + +任意单个 list、set 或 map 字段可接受的最大元素数。用于防止畸形消息触发失控的内存分配。 + +```dart +final fory = Fory(maxCollectionSize: 100000); +``` + +### `maxBinarySize` + +任意单个二进制 blob 字段允许接受的最大字节数。 + +```dart +final fory = Fory(maxBinarySize: 8 * 1024 * 1024); +``` + +## 默认值 + +| 选项 | 默认值 | +| -------------------- | --------- | +| `compatible` | `false` | +| `checkStructVersion` | `true` | +| `maxDepth` | 256 | +| `maxCollectionSize` | 1 048 576 | +| `maxBinarySize` | 64 MiB | + +## 跨语言说明 + +当 Fory 用于不同语言实现的服务之间通信时: + +- 如果任意一端需要 Schema 演进,则**所有**端都应设置 `compatible: true`。 +- 每一端都要使用相同的数字 ID,或者相同的 `namespace + typeName` 组合。 +- 写端和读端的 `compatible` 设置必须一致,模式不匹配会直接失败。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/cross-language.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/cross-language.md new file mode 100644 index 000000000..08560d943 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/cross-language.md @@ -0,0 +1,193 @@ +--- +title: 跨语言序列化 +sidebar_position: 9 +id: dart_cross_language +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Apache Fory™ Dart 生成的二进制格式与 Java、Go、C#、Python、Rust 和 Swift 的 Fory 运行时保持一致。你可以在 Dart 中写消息,在 Java 中读取,或者反过来,整个过程都不需要额外的转换层。 + +## 设置 + +像平常一样创建 `Fory` 实例即可。Dart 中不需要单独开启“跨语言模式”: + +```dart +final fory = Fory(); // 或者在需要 Schema 演进时使用 Fory(compatible: true) +``` + +关键要求是:通信两端必须用同一身份注册同一个类型。 + +## 注册身份 + +最重要的规则是:**每一端都要使用相同的类型身份**。你有两种选择: + +### 数字 ID + +更适合小团队、强协同的场景: + +```dart +// Dart +ModelsFory.register(fory, Person, id: 100); +``` + +### Namespace + Type Name + +更适合多个团队分别定义类型的场景: + +```dart +// Dart +ModelsFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', +); +``` + +不要在不同运行时上对同一个类型混用这两种策略。 + +## Dart 到 Java 示例 + +### Dart + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + Int32 age = Int32(0); +} + +final fory = Fory(); +PersonFory.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = Int32(30)); +``` + +### Java + +```java +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(bytesFromDart); +``` + +## Dart 到 C# 示例 + +### Dart + +```dart +final fory = Fory(compatible: true); +PersonFory.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = Int32(30)); +``` + +### CSharp + +```csharp +[ForyObject] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); + +fory.Register<Person>(100); +Person person = fory.Deserialize<Person>(payloadFromDart); +``` + +## Dart 到 Go 示例 + +### Dart + +```dart +final fory = Fory(); +PersonFory.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = Int32(30)); +``` + +### Go + +```go +type Person struct { + Name string + Age int32 +} + +f := fory.New(fory.WithXlang(true)) +_ = f.RegisterStruct(Person{}, 100) + +var person Person +_ = f.Deserialize(bytesFromDart, &person) +``` + +## 字段匹配规则 + +Fory 会按字段名或稳定字段 ID 匹配字段。为了获得稳健的跨语言互操作性: + +1. 各端对同一类型使用相同的类型身份,即相同的数字 ID 或相同的 `namespace + typeName`。 +2. 在首次对外发送载荷之前,为所有字段分配稳定的 `@ForyField(id: ...)`。 +3. 保持字段名一致,或者依赖字段 ID,因为 Dart 通常使用 `lowerCamelCase`,Go 为导出字段使用 `PascalCase`,C# 也常用 `PascalCase` 属性。 +4. 使用兼容的数字类型:当对端为 Java `int`、Go `int32` 或 C# `int` 时,在 Dart 中使用 `Int32`;Dart 的 `double` 对应 64 位浮点;32 位浮点请使用 `Float32`。 +5. 日期时间字段优先使用 `Timestamp` 和 `LocalDate`,不要直接用原始 `DateTime`。 +6. 发布前一定要在所有目标语言间完成真实 round trip 验证。 + +## Dart 的类型映射说明 + +由于 Dart `int` 本身并不承诺精确的跨语言编码宽度,所以一旦跨语言解释需要精确定义,就应优先使用包装类型或数字字段注解: + +- `Int32` 对应 xlang `int32` +- `UInt32` 对应 xlang `uint32` +- `Float16` 和 `Float32` 对应用于较小宽度的浮点数 +- `Timestamp` 和 `LocalDate` 用于显式的时间语义 + +参见 [支持的类型](supported-types.md) 和 [xlang type mapping](../../specification/xlang_type_mapping.md)。 + +## 验证 + +在生产环境依赖跨语言契约之前,请让每一种支持的运行时都完成端到端验证。 + +运行 Dart 端: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +## 相关主题 + +- [类型注册](type-registration.md) +- [Schema 演进](schema-evolution.md) +- [跨语言指南](../xlang/index.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/custom-serializers.md new file mode 100644 index 000000000..43052efd7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/custom-serializers.md @@ -0,0 +1,139 @@ +--- +title: 自定义序列化器 +sidebar_position: 5 +id: dart_custom_serializers +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +自定义序列化器让你可以完全控制某个类型如何编码和解码。通常只有在以下情况才需要使用它: + +- 类型来自你无法修改的第三方包,无法添加 `@ForyStruct()` +- 你需要完全自定义的二进制布局 +- 你要实现 union / discriminated type + +对于你自己的模型,`@ForyStruct()` 配合代码生成几乎总是更好的选择。 + +## 实现 `Serializer<T>` + +继承 `Serializer<T>` 并实现 `write` 和 `read`。通过 `context.buffer` 直接读写原始字节: + +```dart +import 'package:fory/fory.dart'; + +final class Person { + Person(this.name, this.age); + + final String name; + final int age; +} + +final class PersonSerializer extends Serializer<Person> { + const PersonSerializer(); + + @override + void write(WriteContext context, Person value) { + final buffer = context.buffer; + buffer.writeUtf8(value.name); + buffer.writeInt64(value.age); + } + + @override + Person read(ReadContext context) { + final buffer = context.buffer; + return Person(buffer.readUtf8(), buffer.readInt64()); + } +} +``` + +在使用前先注册这个序列化器: + +```dart +final fory = Fory(); +fory.registerSerializer( + Person, + const PersonSerializer(), + namespace: 'example', + typeName: 'Person', +); +``` + +## 写入嵌套对象 + +如果自定义序列化器中的某个字段本身也是由 Fory 管理的类型,请使用 `context.writeRef` 和 `context.readRef`,而不是递归调用 `fory.serialize`。这样才能保持引用跟踪正确,也避免在嵌套载荷里再写入完整根帧。 + +```dart +@override +void write(WriteContext context, Wrapper value) { + context.writeRef(value.child); +} + +@override +Wrapper read(ReadContext context) { + return Wrapper(context.readRef() as Child); +} +``` + +如果你明确知道某个嵌套值永远不会在对象图中重复出现,也不需要保持引用标识,可以用 `writeNonRef`: + +```dart +context.writeNonRef(value.child); +``` + +## Union + +对于带判别标签的 union,请继承 `UnionSerializer<T>` 而不是 `Serializer<T>`。先写入判别值,再写入当前激活的变体;读取时先解析判别值,再分派到正确分支。 + +```dart +final class ShapeSerializer extends UnionSerializer<Shape> { + const ShapeSerializer(); + + @override + void write(WriteContext context, Shape value) { + // write active variant + } + + @override + Shape read(ReadContext context) { + // read discriminant, return correct variant + throw UnimplementedError(); + } +} +``` + +## 自定义序列化器中的循环引用 + +如果你的序列化器会遇到循环对象图,那么在读取嵌套字段之前,必须先把对象绑定到引用跟踪器中: + +```dart +final value = Node.empty(); +context.reference(value); // register the object first +value.next = context.readRef() as Node?; // now nested reads can refer back to it +return value; +``` + +跳过这一步会导致指向该对象的回溯引用解析成 `null`。 + +## 提示 + +- 在热点路径中,优先使用 `context.buffer` 直接读写字节。 +- 在所有端上,都用相同的身份注册该序列化器,即相同的 `id` 或相同的 `namespace + typeName`。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [跨语言](cross-language.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/field-configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/field-configuration.md new file mode 100644 index 000000000..a5a414cd4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/field-configuration.md @@ -0,0 +1,127 @@ +--- +title: 字段配置 +sidebar_position: 6 +id: dart_field_configuration +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +在 `@ForyStruct()` 类中的字段上添加 `@ForyField(...)`,即可改变该字段的序列化方式。 + +## 快速参考 + +```dart +@ForyField( + skip: false, // exclude the field from serialization + id: 10, // stable field ID for schema evolution + nullable: true, // override nullability detection + ref: true, // enable reference tracking for this field + dynamic: false, // control whether the runtime type is written +) +``` + +## `skip` + +完全把该字段排除在序列化之外。适合缓存值、计算值或仅用于 UI 的值,这些值不应该进入持久化或传输消息。 + +```dart +@ForyField(skip: true) +String cachedDisplayName = ''; +``` + +## `id` + +为字段分配稳定身份,这样在 Schema 变化后,例如字段重命名或重排时,Fory 仍然可以通过 ID 匹配它。**如果你未来可能新增、删除或重命名字段,请现在就为所有字段分配 ID**,而且要在第一份载荷发出之前完成。 + +```dart +@ForyField(id: 1) +String name = ''; +``` + +一旦载荷已经在服务之间共享,就永远不要把某个 `id` 复用到另一个不同字段上。 + +## `nullable` + +显式声明字段是可空还是非可空,从而覆盖 Fory 根据 Dart 类型做出的推断。当 Dart 类型是非可空,但你仍希望 Fory 在线路上接受 `null` 时,可以使用它,例如为了读取旧生产者生成、可能省略该字段的消息。 + +```dart +@ForyField(nullable: true) +String nickname = ''; +``` + +在跨语言场景下,也要确保可空性契约与对端运行时的预期一致。 + +## `ref` + +为某个字段启用引用跟踪。当对象图中的多个对象可能引用同一个实例,或该字段类型本身可能形成循环时,请使用这个选项。没有 `ref: true` 时,如果同一个对象出现在两个字段里,Fory 会把它的值序列化两次。 + +```dart +@ForyField(ref: true) +List<Object?> sharedNodes = <Object?>[]; +``` + +注意:即使设置了 `ref: true`,像 `int`、`double`、`bool` 这样的标量类型也不会从引用跟踪中受益。 + +## `dynamic` + +控制 Fory 是否把字段值的具体运行时类型写入载荷。 + +- `null`(默认):Fory 根据声明类型自动决定。 +- `false`:始终使用字段的声明类型,载荷更紧凑,但反序列化端必须知道精确类型。 +- `true`:始终写入实际运行时类型;当字段声明为 `Object?` 或基类,但运行时可能持有多种具体类型时,这是必需的。 + +```dart +@ForyField(dynamic: true) +Object? payload; // can hold any registered type at runtime +``` + +## 数字字段注解 + +Dart `int` 在运行时是 64 位值。当你与 Java、Go 或 C# 交换消息时,对端可能期望更窄的整数类型。可以使用数字注解来固定精确的编码格式: + +```dart +@ForyStruct() +class Sample { + Sample(); + + @Int32Type(compress: false) // always writes 4 bytes + int fixedWidthInt = 0; + + @Int64Type(encoding: LongEncoding.tagged) // variable-length encoding + int compactLong = 0; + + @Uint32Type(compress: true) // variable-length unsigned + int smallUnsigned = 0; +} +``` + +可用注解包括:`@Int32Type`、`@Int64Type`、`@Uint8Type`、`@Uint16Type`、`@Uint32Type`、`@Uint64Type`。 + +或者,也可以使用 [支持的类型](supported-types.md) 中介绍的显式包装类型,例如 `Int32`、`UInt32` 等。 + +## 跨语言字段对齐 + +当同一个模型在多种语言中分别定义时: + +- 为所有未来可能变化的字段分配稳定 `id`。 +- 对真正具备多态性的字段使用 `dynamic: true`。 +- 保持字段的逻辑含义在各语言之间一致。Fory 可以按名称或 ID 匹配字段,但无法替你弥合语义上的差异。 + +## 相关主题 + +- [代码生成](code-generation.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md new file mode 100644 index 000000000..fab52fbba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md @@ -0,0 +1,141 @@ +--- +title: Dart 序列化指南 +sidebar_position: 0 +id: dart_serialization_index +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Apache Fory™ Dart 可以把 Dart 对象序列化为字节,再从字节反序列化回来,并且支持与 Java、Go、C#、Python 以及其他 Fory 支持语言编写的服务进行互通。 + +## 为什么选择 Fory Dart? + +- **跨语言**:在 Dart 中序列化,在 Java、Go、C# 等语言中反序列化,无需额外胶水代码 +- **高性能**:生成的序列化代码会替代运行时反射 +- **Schema 演进**:可以新增或删除字段,而不破坏已有消息 +- **循环引用**:可选的引用跟踪可处理共享或递归对象图 +- **逃生口**:对任何无法加注解的类型,你都可以手写序列化器 + +## 快速开始 + +### 要求 + +- Dart SDK 3.6 或更高版本 +- `build_runner`,用于生成序列化代码 + +### 安装 + +把依赖加入你的 `pubspec.yaml`: + +```yaml +dependencies: + fory: ^0.17.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +### 基础示例 + +定义模型,先运行一次生成器,然后进行序列化: + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +enum Color { + red, + blue, +} + +@ForyStruct() +class Person { + Person(); + + String name = ''; + Int32 age = Int32(0); + Color favoriteColor = Color.red; + List<String> tags = <String>[]; +} + +void main() { + final fory = Fory(); + PersonFory.register( + fory, + Color, + namespace: 'example', + typeName: 'Color', + ); + PersonFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', + ); + + final person = Person() + ..name = 'Ada' + ..age = Int32(36) + ..favoriteColor = Color.blue + ..tags = <String>['engineer', 'mathematician']; + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize<Person>(bytes); + print(roundTrip.name); +} +``` + +在运行程序之前,先生成配套文件: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +`PersonFory` 由 `build_runner` 生成。`namespace` 和 `typeName` 是其他语言中的对端识别同一类型的方式,一旦服务进入生产环境,就应保持稳定。 + +## API 概览 + +- `Fory(...)`:创建序列化实例;创建一次并复用 +- `fory.serialize(value)`:返回 `Uint8List` 字节 +- `fory.deserialize<T>(bytes)`:返回一个 `T` +- `@ForyStruct()`:标记需要生成代码的类 +- `@ForyField(...)`:字段级选项,例如跳过、ID、可空性、引用 +- 整数包装类型:`Int8`、`Int16`、`Int32`、`UInt8`、`UInt16`、`UInt32` +- 浮点包装类型:`Float16`、`Float32` +- 时间包装类型:`LocalDate`、`Timestamp` + +## 文档 + +| 主题 | 说明 | +| --------------------------------------------- | ------------------------------------ | +| [配置](configuration.md) | 运行时选项、兼容模式和安全限制 | +| [基础序列化](basic-serialization.md) | `serialize`、`deserialize`、生成注册、根对象图 | +| [代码生成](code-generation.md) | `@ForyStruct`、build runner 和生成命名空间 | +| [类型注册](type-registration.md) | 基于 ID 与基于名称的注册,以及注册规则 | +| [自定义序列化器](custom-serializers.md) | 手写 `Serializer<T>` 实现与 union | +| [字段配置](field-configuration.md) | `@ForyField`、字段 ID、可空性、引用、多态 | +| [支持的类型](supported-types.md) | 内置 xlang 值、包装类型、集合和结构体 | +| [Schema 演进](schema-evolution.md) | 兼容结构体与可演进 Schema | +| [跨语言](cross-language.md) | 互操作规则与字段对齐 | +| [故障排查](troubleshooting.md) | 常见错误、诊断方法和验证步骤 | + +## 相关资源 + +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) +- [Xlang 实现指南](../../specification/xlang_implementation_guide.md) +- [跨语言指南](../xlang/index.md) +- [Dart 运行时源码目录](https://github.com/apache/fory/tree/main/dart) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/schema-evolution.md new file mode 100644 index 000000000..267cd63f3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/schema-evolution.md @@ -0,0 +1,92 @@ +--- +title: Schema 演进 +sidebar_position: 8 +id: dart_schema_evolution +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Schema 演进让应用的不同版本之间也能安全交换消息。例如,v2 写出的消息仍然可以被 v1 读取,反过来也一样。 + +## 两种模式 + +### 兼容模式(推荐给会演进的服务) + +当服务可能同时运行不同版本时启用它,例如滚动发布期间,或者客户端无法立即升级时。 + +```dart +final fory = Fory(compatible: true); +``` + +在兼容模式下,Fory 会在每条消息中写入足够的字段元信息,使读端能够跳过未知字段,并为缺失字段使用默认值。请用稳定字段 ID 来锚定跨版本 Schema。 + +### Schema 一致模式(默认) + +通信双方必须拥有同一个模型。Fory 会校验双方 Schema 是否一致,并拒绝来自其他 Schema 版本的消息。适用于所有服务总是一起升级,并且你希望尽早把 Schema 不匹配直接报错的场景。 + +```dart +final fory = Fory(); // compatible: false by default +``` + +## 为演进做好准备 + +为了安全地使用兼容模式,请给结构体添加 `@ForyStruct(evolving: true)`(默认值),并在第一次对外发送载荷之前,为每个字段都分配稳定的 `@ForyField(id: ...)`: + +```dart +@ForyStruct(evolving: true) +class UserProfile { + UserProfile(); + + @ForyField(id: 1) + String name = ''; + + @ForyField(id: 2, nullable: true) + String? nickname; +} +``` + +如果载荷已经在生产环境中存在之后你才补加字段 ID,那么旧消息里不会包含这些 ID,Schema 演进也就无法正确工作。 + +## 哪些变更是安全的 + +**安全变更**(双方兼容): + +- 新增一个带新字段 ID 的可选字段 +- 重命名字段,只要 `@ForyField(id: ...)` 保持不变 +- 删除字段,对端会忽略缺失值并使用 Dart 默认值 + +**危险变更**(可能破坏已有消息): + +- 把一个已有字段 ID 复用给另一个不同字段 +- 将字段类型改为不兼容类型,例如 `Int32` 改成 `String` +- 在消息进入生产环境后修改某个类型的注册身份,即 `id`、`namespace` 或 `typeName` +- 不改字段 ID,却改变字段的逻辑含义 + +## 跨语言说明 + +只有在**所有**交换消息的运行时都一致满足以下条件时,Schema 演进才会生效: + +1. 使用相同的 `compatible` 设置。 +2. 使用相同的类型注册身份,即相同的数字 ID,或者相同的 `namespace + typeName`。 +3. 对字段 ID 的逻辑含义有相同理解。 + +部署前请用真实 round trip 覆盖滚动升级场景。 + +## 相关主题 + +- [配置](configuration.md) +- [字段配置](field-configuration.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/supported-types.md new file mode 100644 index 000000000..a6d996ce4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/supported-types.md @@ -0,0 +1,102 @@ +--- +title: 支持的类型 +sidebar_position: 7 +id: dart_supported_types +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页列出可在 Fory 消息中使用的 Dart 类型,并标出哪些地方在跨语言兼容性上需要特别小心。 + +## 内置原始类型 + +以下 Dart 类型可以直接序列化,不需要特殊处理: + +| Dart 类型 | 跨语言说明 | +| -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `bool` | 直接映射 | +| `int` | 默认按 64 位序列化。如果对端期望更窄的整数,请使用包装类型或 `@Int32Type` 等注解 | +| `double` | 映射为 64 位浮点数。如果对端期望 32 位浮点,请使用 `Float32` 包装类型 | +| `String` | 直接映射 | +| `Uint8List` | 二进制 blob | +| `List`, `Set`, `Map` | 支持,但元素类型也必须是受支持类型 | +| `DateTime` | 如需明确语义,请使用 `Timestamp` 或 `LocalDate` 包装类型 | + +## 整数包装类型 + +Dart `int` 在运行时是 64 位值。如果对端语言期望 32 位整数,例如 Java `int`、Go `int32`、C# `int`,而你发送的是 Dart `int`,反序列化可能失败,或者静默截断。 + +使用整数包装类型可以固定精确的编码宽度: + +```dart +final Int8 tiny = Int8(-1); // 8-bit signed +final Int16 shortValue = Int16(7); // 16-bit signed +final Int32 age = Int32(36); // 32-bit signed — matches Java int, C# int, Go int32 +final UInt8 flags = UInt8(255); // 8-bit unsigned +final UInt16 port = UInt16(65535); // 16-bit unsigned +final UInt32 count = UInt32(4000000000); // 32-bit unsigned +``` + +每个包装类型都会把存储值限制在目标位宽内。 + +## 浮点包装类型 + +Dart `double` 对应 64 位浮点。如果对端使用 32 位浮点,请改用包装类型: + +- `Float32`:32 位浮点,对应 Java `float`、C# `float`、Go `float32` +- `Float16`:半精度浮点,适用于专门的数值载荷 + +## 时间和日期类型 + +不要直接把原始 `DateTime` 跨语言发送。时区处理和 epoch 差异在不同语言间并不完全一致。请改用下面这些显式包装类型: + +- `Timestamp`:带纳秒精度的 UTC 时间点,即秒数加纳秒数 +- `LocalDate`:不带时间和时区的日历日期 + +```dart +final now = Timestamp.fromDateTime(DateTime.now().toUtc()); +final birthday = LocalDate(1990, 12, 1); +``` + +## Struct 和 Enum + +给类添加 `@ForyStruct()`,然后运行 `build_runner`,它们就能序列化。定义在同一文件中的枚举会自动包含进去。 + +```dart +@ForyStruct() +class User { + User(); + + String name = ''; + Int32 age = Int32(0); // use Int32 when peers expect a 32-bit integer +} +``` + +参见 [代码生成](code-generation.md)。 + +## 集合 + +Fory 支持 `List<T>`、`Set<T>` 和 `Map<K, V>`。元素类型和键类型本身也必须可序列化。请避免使用可变对象作为 map 键。 + +## 兼容性提示 + +一旦不确定某个 Dart 类型是否与对端预期一致,就优先使用显式包装类型。数字宽度选错,是跨语言场景里最常见的 bug 之一。 + +## 相关主题 + +- [字段配置](field-configuration.md) +- [跨语言](cross-language.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/troubleshooting.md new file mode 100644 index 000000000..5be8b796a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/troubleshooting.md @@ -0,0 +1,103 @@ +--- +title: 故障排查 +sidebar_position: 10 +id: dart_troubleshooting +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页汇总 Dart 运行时中常见的问题及其修复方式。 + +## `Only xlang payloads are supported by the Dart runtime.` + +写端发送的是 native-mode(非 xlang)载荷。请确保每个服务都走跨语言兼容路径: + +- **Java**:在 Fory builder 上添加 `.withLanguage(Language.XLANG)`。 +- **Go**:在 Fory 选项中使用 `WithXlang(true)`。 +- **其他运行时**:查看各自文档,确认如何启用跨语言模式。 + +## `Type ... is not registered.` + +Fory 不知道如何序列化或反序列化这个类型。可按以下方式修复: + +1. 如果还没生成代码,先运行:`dart run build_runner build --delete-conflicting-outputs` +2. 在调用 `serialize` 或 `deserialize` **之前**,先调用生成的 `register` 函数,或者 `registerSerializer` +3. 注册消息中可能出现的**所有**类型,而不仅仅是根类型。例如,如果 `Order` 包含 `Address`,那两者都要注册 + +## 生成的 part 文件缺失或已过期 + +重新生成代码: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +``` + +如果你移动了文件或重命名了类型,请在重新执行分析或测试前先重新构建。 + +## `Deserialized value has type ..., expected ...` + +载荷描述的类型与 `deserialize<T>` 中的 `T` 不一致。常见原因包括: + +- 写端注册该类型时使用的 ID 或名称,与读端不一致 +- 载荷来自另一条代码路径,根对象类型不同 +- 你正在反序列化异构容器。应先按 `Object?` 或 `List<Object?>` 解码,再做类型转换 + +## 反序列化后对象不再是同一个实例 + +默认情况下,Fory 不会跟踪对象标识,因此两个字段如果指向同一个对象,round trip 后会变成两个独立副本。 + +如果需要保留对象标识: + +- 对 `@ForyStruct` 内部字段,在对应字段上加 `@ForyField(ref: true)` +- 对顶层集合,调用 `fory.serialize(...)` 时传入 `trackRef: true` +- 在自定义序列化器中,使用 `context.writeRef` / `context.readRef`,并在读取嵌套字段之前先调用 `context.reference(obj)` + +## 跨语言字段不匹配(数据缺失或值错误) + +典型症状:往返另一种语言后,字段变成默认值,或者类型错误。 + +检查清单: + +1. 双方使用相同的注册身份,即相同数字 ID,**或**相同的 `namespace + typeName` +2. 在第一份载荷发送前,就已经分配了稳定 `@ForyField(id: ...)` +3. 数字宽度兼容。当对端字段是 Java `int`、Go `int32` 或 C# `int` 时,在 Dart 端使用 `Int32` +4. 日期时间字段使用 `Timestamp` / `LocalDate`,而不是原始 `DateTime` +5. 如果用到 Schema 演进,则**双方**都要开启 `compatible: true` + +## 本地运行测试 + +主 Dart 包: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +集成测试包: + +```bash +cd dart/packages/fory-test +dart run build_runner build --delete-conflicting-outputs +dart test +``` + +## 相关主题 + +- [跨语言](cross-language.md) +- [代码生成](code-generation.md) +- [自定义序列化器](custom-serializers.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/type-registration.md new file mode 100644 index 000000000..bd5f7e097 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/type-registration.md @@ -0,0 +1,98 @@ +--- +title: 类型注册 +sidebar_position: 4 +id: dart_type_registration +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Fory 需要知道序列化消息中的某个类型对应哪个类。你要做的,就是在序列化或反序列化之前注册每个类。 + +## 选择注册策略 + +Fory 提供两种策略。选定一种后,就要在所有读写该类型的语言里保持一致。 + +### 策略一:数字 ID + +更紧凑,也更快。适合小团队在服务间统一协调 ID。 + +```dart +ModelsFory.register(fory, User, id: 100); +``` + +其他语言里也必须使用相同的数字: + +```java +// Java side +fory.register(User.class, 100); +``` + +### 策略二:Namespace + Type Name + +自描述性更强。适合多个团队或多个包独立定义类型,而协调数字 ID 不现实的场景。 + +```dart +ModelsFory.register( + fory, + User, + namespace: 'example', + typeName: 'User', +); +``` + +每个读写该类型的运行时都必须使用相同的 `namespace` 和 `typeName`。 + +> **不要对同一个类型混用策略。** 如果一侧使用数字 ID,另一侧使用名称,反序列化会失败。 + +## 注册生成类型 + +调用 `.fory.dart` 文件中生成的 `register` 函数,它会为你安装好所需的全部序列化元信息: + +```dart +UserModelsFory.register(fory, User, id: 100); +``` + +## 注册自定义序列化器 + +对于无法添加 `@ForyStruct()` 的类型,可以直接传入序列化器实例: + +```dart +fory.registerSerializer( + ExternalType, + const ExternalTypeSerializer(), + namespace: 'example', + typeName: 'ExternalType', +); +``` + +关于如何实现序列化器,见 [自定义序列化器](custom-serializers.md)。 + +## 必须遵守的规则 + +- 在第一次调用 `serialize` 或 `deserialize` **之前**完成注册 +- 注册消息中可能出现的**每一个**类,而不仅是根类型 +- 一旦载荷已经持久化,或已经在服务间交换,就必须保持 ID 或名称**稳定** +- 对同一个类型,不要一侧用数字 ID,另一侧用名称 + +## 跨语言要求 + +所有读写该类型的运行时都必须使用相同的数字 ID,或者相同的 `namespace + typeName` 组合。示例见 [跨语言](cross-language.md)。 + +## 相关主题 + +- [代码生成](code-generation.md) +- [跨语言](cross-language.md) +- [自定义序列化器](custom-serializers.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/enum-configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/enum-configuration.md new file mode 100644 index 000000000..0b168034e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/enum-configuration.md @@ -0,0 +1,104 @@ +--- +title: 枚举配置 +sidebar_position: 6 +id: enum_configuration +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页说明如何在 Apache Fory 中配置 Java 枚举的序列化方式。 + +## 默认枚举行为 + +Java 枚举可以通过两种模式进行序列化: + +1. **按数值 tag**:默认行为 +2. **按枚举名称**:通过 `serializeEnumByName(true)` 启用 + +在 xlang 模式下,始终使用数值 tag。在原生 Java 模式下,`serializeEnumByName(true)` 会把枚举序列化方式切换为按名称,而不是按数值 tag。 + +## 按名称序列化枚举 + +当原生 Java 对端应按名称而不是按数值 tag 匹配枚举常量时,请使用 `serializeEnumByName(true)`。 + +```java +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .serializeEnumByName(true) + .build(); +``` + +当声明顺序可能不稳定,但枚举名称保持固定时,这种模式很有用。它只影响原生 Java 模式;xlang 仍然使用数值 tag。 + +## 稳定的数值枚举 ID + +如果未启用 `serializeEnumByName(true)`,Java 枚举会按数值 tag 序列化。默认的 tag 是声明顺序对应的 ordinal。如果枚举需要与声明顺序无关的稳定 ID,请只使用一种 `@ForyEnumId` 来源:要么标注唯一一个 ID 来源,要么为每个枚举常量都显式标注 tag 值。 + +```java +import org.apache.fory.annotation.ForyEnumId; + +enum Status { + Unknown(10), + Running(20), + Finished(30); + + private final int id; + + Status(int id) { + this.id = id; + } + + @ForyEnumId + public int getId() { + return id; + } +} +``` + +Java 也支持在某个枚举实例字段上标注 `@ForyEnumId`,或者直接在每个枚举常量上标注,例如 `@ForyEnumId(10) Unknown`。 + +### `@ForyEnumId` 的三种样式 + +`@ForyEnumId` 只支持以下三种配置方式: + +1. 标注一个枚举实例字段,并在该字段中保存数值 ID +2. 标注一个无参 public 实例方法,例如 `getId()` +3. 为每个枚举常量都直接标注显式值,例如 `@ForyEnumId(10) Unknown` + +### 校验规则 + +1. 对于同一个枚举,只能使用上述三种方式中的一种 +2. 字段和方法上的注解必须保持 `value()` 为默认值 `-1` +3. 一旦有任意一个枚举常量使用 `@ForyEnumId`,所有枚举常量都必须标注 +4. 所有 ID 都必须非负、唯一,并且能放入 Java `int` + +### 查找行为 + +1. 没有 `@ForyEnumId` 时,Fory 写入声明顺序对应的 ordinal +2. 使用 `@ForyEnumId` 时,Fory 会改为写入配置好的稳定数值 tag +3. 对于较小且稠密的 tag,内部会使用数组查找;对稀疏且较大的 tag,则退回到 map 查找 + +## 如何在名称模式和数值模式之间选择 + +- 当枚举只在 Java 内部使用,且常量名才是兼容性主键时,使用**枚举名称** +- 当需要处理跨语言载荷,或需要稳定的显式 ID 时,使用**数值 tag** +- 当声明顺序可能变化,但编码格式中的数值 ID 必须保持稳定时,使用 **`@ForyEnumId`** + +## 相关主题 + +- [配置选项](configuration.md) - `serializeEnumByName` 及其他运行时选项 +- [字段配置](field-configuration.md) - `@ForyField`、`@Ignore` 和整数编码注解 +- [跨语言序列化](cross-language.md) - Xlang 枚举互操作 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/virtual-threads.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/virtual-threads.md new file mode 100644 index 000000000..40f46007c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/virtual-threads.md @@ -0,0 +1,114 @@ +--- +title: 虚拟线程 +sidebar_position: 8 +id: java_virtual_threads +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Apache Fory Java 在虚拟线程工作负载下应使用 `buildThreadSafeFory()`。它会构建一个固定大小的共享 `ThreadPoolFory`,其大小为 `4 * availableProcessors()`。如果你需要不同的固定池大小,请使用 `buildThreadSafeForyPool(poolSize)`。 + +## 使用二进制输入/输出 API + +在使用虚拟线程时,应始终使用 Fory 的二进制输入/输出 API: + +- `serialize(Object)` 或 `serialize(MemoryBuffer, Object)` +- `deserialize(byte[])` 或 `deserialize(MemoryBuffer)` + +典型用法: + +```java +ThreadSafeFory fory = Fory.builder() + .requireClassRegistration(false) + .buildThreadSafeFory(); + +byte[] bytes = fory.serialize(request); +Object value = fory.deserialize(bytes); +``` + +## 在大量虚拟线程场景下不要使用 Stream API + +对于大量依赖虚拟线程的工作负载,不要使用基于 stream 或 channel 的 API: + +- `serialize(OutputStream, Object)` +- `deserialize(ForyInputStream)` +- `deserialize(ForyReadableChannel)` + +这些 API 会在整个阻塞调用期间一直占用一个池化的 `Fory` 实例。在大量虚拟线程场景下,这意味着许多 `Fory` 实例会在等待 I/O 时持续处于占用状态。每个 `Fory` 实例通常会使用大约 `30~50 KB` 内存,因此在阻塞 I/O 期间保留大量实例会很快累积出明显的内存开销。 + +只有当你的虚拟线程数量至多是几百个,并且这些额外保留的 `Fory` 内存仍然可以接受时,才建议在虚拟线程中使用 stream API。 + +## 为什么二进制 API 更合适 + +序列化和反序列化属于 CPU 工作。Fory 本身足够快,因此这部分 CPU 时间通常远短于网络传输时间。 + +在大多数情况下,你并不需要让网络传输与 Fory 反序列化重叠执行。Fory 反序列化的耗时通常不到网络传输时间的 `1/10`,因此相比尝试通过 Fory 逐步流式处理一个对象图,优化传输路径往往更重要。 + +大多数 RPC 系统本身也是基于带帧的字节消息,而不是 Java 对象流。例如,gRPC 使用长度定界帧,这与 Fory 的二进制 API 天然契合。 + +一个适合虚拟线程的模式是: + +1. 读取一条完整的带帧消息到字节数组中 +2. 调用 `fory.deserialize(bytes)` +3. 生成响应对象 +4. 调用 `fory.serialize(response)` +5. 将响应字节写成下一段带帧数据 + +## 推荐模式 + +```java +byte[] requestBytes = readOneFrame(channel); +Request request = (Request) fory.deserialize(requestBytes); + +Response response = handle(request); +byte[] responseBytes = fory.serialize(response); +writeOneFrame(channel, responseBytes); +``` + +这种方式让 Fory 只参与高效的 CPU 密集部分,而把阻塞 I/O 留在序列化器之外。 + +## 超大载荷:分块的长度定界流式处理 + +对于大多数场景,上述常规的带帧字节模式已经足够。只有在载荷非常大,并且你希望让传输与序列化/反序列化重叠时,才需要考虑分块流式处理。 + +即便如此,也不要使用 Fory 自带的 stream API。正确做法是:把一个大载荷拆成多个子对象图,将每个子对象图分别序列化为 `byte[]`,然后按以下顺序写出: + +1. 帧长度 +2. 分块字节 + +在虚拟线程中进行反序列化时: + +1. 读取帧长度 +2. 精确读取对应字节数 +3. 调用 `fory.deserialize(chunkBytes)` + +这样可以让传输按块推进,而 Fory 始终只处理完整的二进制帧。 + +```java +for (Object chunk : splitIntoSubGraphs(largePayload)) { + byte[] bytes = fory.serialize(chunk); + writeFrame(output, bytes); +} + +while (hasMoreFrames(input)) { + int length = readLength(input); + byte[] bytes = readBytes(input, length); + Object chunk = fory.deserialize(bytes); + consumeChunk(chunk); +} +``` + +长度定界帧非常常见,gRPC 也使用长度定界帧而不是 Java 对象流,因此这种模式与典型 RPC 和虚拟线程传输模型非常契合。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/_category_.json new file mode 100644 index 000000000..2434e92ab --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "JavaScript", + "position": 6, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/basic-serialization.md new file mode 100644 index 000000000..78d3926d3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/basic-serialization.md @@ -0,0 +1,219 @@ +--- +title: 基础序列化 +sidebar_position: 1 +id: basic_serialization +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本指南介绍 Apache Fory JavaScript 中的核心序列化 API。 + +## 创建 `Fory` 实例 + +```ts +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +``` + +创建一个实例,注册你的 schema,并重复复用它。Fory 会在首次调用 `register` 后缓存生成的序列化器,因此如果每个请求都重新创建实例,就会浪费这部分工作。 + +## 使用 `Type.struct` 定义 Schema + +最常见的方式是先定义 schema,再进行注册。 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const accountType = Type.struct( + { typeName: "example.account" }, + { + id: Type.int64(), + owner: Type.string(), + active: Type.bool(), + nickname: Type.string().setNullable(true), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(accountType); +``` + +## 序列化与反序列化 + +```ts +const bytes = serialize({ + id: 42n, + owner: "Alice", + active: true, + nickname: null, +}); + +const value = deserialize(bytes); +console.log(value); +// { id: 42n, owner: 'Alice', active: true, nickname: null } +``` + +返回的 `bytes` 值是 `Uint8Array`/平台缓冲区,可以通过网络发送或写入存储。 + +## 根级动态序列化 + +`Fory` 也支持在不先绑定特定 schema 序列化器的情况下,对动态根值进行序列化。 + +```ts +const fory = new Fory(); + +const bytes = fory.serialize( + new Map([ + ["name", "Alice"], + ["age", 30], + ]), +); + +const value = fory.deserialize(bytes); +``` + +这对动态载荷很方便,但对于稳定接口和跨语言契约,显式 schema 通常更合适。 + +## 原始值 + +```ts +const fory = new Fory(); + +fory.deserialize(fory.serialize(true)); +// true + +fory.deserialize(fory.serialize("hello")); +// 'hello' + +fory.deserialize(fory.serialize(123)); +// 123 + +fory.deserialize(fory.serialize(123n)); +// 123n + +fory.deserialize(fory.serialize(new Date("2021-10-20T09:13:00Z"))); +// Date +``` + +### `number` 与 `bigint` + +JavaScript 的 `number` 是 64 位浮点数,无法精确表示所有 64 位整数。对于跨语言契约,或任何需要精确整数宽度的场景,请在 schema 中使用显式字段类型: + +- `Type.int32()`:32 位整数;使用 JavaScript `number` +- `Type.int64()`:64 位整数;使用 JavaScript `bigint` +- `Type.float32()` / `Type.float64()`:浮点数 + +动态根序列化(即不使用 schema,直接调用 `fory.serialize(someNumber)`)会推断类型,但 API 不保证推断结果稳定。对于任何稳定契约,都应使用 schema。 + +## 数组、Map 和 Set + +```ts +const inventoryType = Type.struct("example.inventory", { + tags: Type.array(Type.string()), + counts: Type.map(Type.string(), Type.int32()), + labels: Type.set(Type.string()), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(inventoryType); + +const bytes = serialize({ + tags: ["hot", "new"], + counts: new Map([ + ["apple", 3], + ["pear", 8], + ]), + labels: new Set(["featured", "seasonal"]), +}); + +const value = deserialize(bytes); +``` + +## 嵌套 Struct + +```ts +const addressType = Type.struct("example.address", { + city: Type.string(), + country: Type.string(), +}); + +const userType = Type.struct("example.user", { + name: Type.string(), + address: Type.struct("example.address", { + city: Type.string(), + country: Type.string(), + }), +}); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ + name: "Alice", + address: { city: "Hangzhou", country: "CN" }, +}); + +const user = deserialize(bytes); +``` + +如果嵌套值可能缺失,请将其标记为可空: + +```ts +const wrapperType = Type.struct("example.wrapper", { + child: Type.struct("example.child", { + name: Type.string(), + }).setNullable(true), +}); +``` + +## 基于 Decorator 的注册 + +TypeScript decorator 也受支持。 + +```ts +import Fory, { Type } from "@apache-fory/core"; + [email protected]("example.user") +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(User); + +const user = new User(); +user.id = 1n; +user.name = "Alice"; + +const copy = deserialize(serialize(user)); +console.log(copy instanceof User); // true +``` + +## 可空性 + +在基于 schema 的 struct 中,字段的可空性需要显式声明。 + +```ts +const nullableType = Type.struct("example.optional_user", { + name: Type.string(), + email: Type.string().setNullable(true), +}); +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/cross-language.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/cross-language.md new file mode 100644 index 000000000..7aa9eb0d5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/cross-language.md @@ -0,0 +1,128 @@ +--- +title: 跨语言序列化 +sidebar_position: 80 +id: cross_language +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Fory JavaScript 与 Java、Python、Go、Rust、Swift 和 C++ 的 Fory 运行时使用相同的二进制格式进行序列化。你可以在 JavaScript 中写入消息,再在 Java 中读取它,反过来也一样,无需额外的转换层。 + +需要注意: + +- Fory JavaScript 运行时只读写跨语言载荷,不支持任何语言原生格式。 +- 当前暂不支持 out-of-band mode。 + +## 成功完成往返的要求 + +要让一条消息能在 JavaScript 与另一种运行时之间稳定往返,双方必须满足: + +1. 两端具有**相同的类型标识**,即相同的数值 ID,或相同的 `namespace + typeName` +2. **字段类型兼容**,例如 JavaScript 中的 `Type.int32()` 字段应对应 Java `int`、Go `int32`、C# `int` +3. **可空性一致**,如果一侧把字段标记为可空,另一侧也应如此 +4. 如果使用 Schema 演进,双方的 `compatible` 模式必须一致 +5. 如果数据包含共享引用或循环引用,双方的引用跟踪配置也必须一致 + +## 分步说明:从 JavaScript 到其他运行时 + +1. 在 JavaScript 中使用与其他运行时相同的类型名称或数值 ID 定义 schema +2. 在两端都注册该 schema +3. 对齐字段类型、可空性和 `compatible` 设置 +4. 在发布前对真实载荷做端到端测试 + +JavaScript 侧: + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const messageType = Type.struct( + { typeName: "example.message" }, + { + id: Type.int64(), + content: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize } = fory.register(messageType); + +const bytes = serialize({ + id: 1n, + content: "hello from JavaScript", +}); +``` + +在另一侧,请使用对应运行时的 API 注册同一个 `example.message` 类型,即相同的名称或相同的数值 ID: + +- [Java guide](../java/index.md) +- [Python guide](../python/index.md) +- [Go guide](../go/index.md) +- [Rust guide](../rust/index.md) + +## 字段命名 + +Fory 按字段名匹配字段。当模型在多种语言中定义时,应保持字段名一致,或者至少采用能够在不同语言间无歧义映射的命名方案,例如统一使用 `snake_case`。 + +当使用 `compatible: true` 进行 Schema 演进时,字段顺序的差异是允许的,但字段名本身仍必须一致。 + +## 数值类型 + +JavaScript 的 `number` 是 64 位浮点数,无法与其他语言中的所有整数类型一一对应。因此应使用显式 schema 类型: + +- `Type.int32()`:用于 32 位整数,对应 Java `int`、Go `int32`、C# `int` +- `Type.int64()`:配合 `bigint` 值使用,用于 64 位整数,对应 Java `long`、Go `int64` +- `Type.float32()` 或 `Type.float64()`:用于浮点数 + +## 日期与时间 + +- `Type.timestamp()`:表示一个时间点;往返后仍是 JavaScript `Date` +- `Type.date()`:表示不带时间的日期;反序列化结果为 `Date` +- `Type.duration()`:在 JavaScript 中暴露为毫秒数 + +## 多态字段 + +`Type.any()` 允许字段在运行时承载不同类型的值,但它在跨语言场景中更难保持一致。只要可能,就应优先使用显式字段 schema。 + +```ts +const wrapperType = Type.struct( + { typeId: 3001 }, + { + payload: Type.any(), + }, +); +``` + +## 枚举 + +枚举成员的**顺序**必须在不同语言间保持一致。Fory 按 ordinal position 而不是按枚举值对枚举进行编码。 + +```ts +const Color = { Red: 1, Green: 2, Blue: 3 }; +const fory = new Fory(); +fory.register(Type.enum({ typeId: 210 }, Color)); +``` + +在每个对端运行时中都应使用相同的类型 ID 或类型名。 + +## 安全限制 + +`maxDepth`、`maxBinarySize` 和 `maxCollectionSize` 这些选项用于保护 JavaScript 运行时,防止接收过大载荷。它们不会改变二进制格式,只决定本地运行时愿意接受什么样的数据。 + +## 相关主题 + +- [支持的类型](supported-types.md) +- [Schema 演进](schema-evolution.md) +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/index.md new file mode 100644 index 000000000..ae4b5b8f8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/index.md @@ -0,0 +1,141 @@ +--- +title: JavaScript 序列化指南 +sidebar_position: 0 +id: index +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Apache Fory JavaScript 让你可以把 JavaScript 和 TypeScript 对象序列化为字节,并再把它们反序列化回来,包括与 Java、Python、Go、Rust、Swift 以及其他 Fory 支持的语言编写的服务进行跨语言互通。 + +## 为什么选择 Fory JavaScript? + +- **跨语言**:可以在 JavaScript 中序列化,在 Java、Python、Go 等语言中反序列化,无需编写胶水代码 +- **高性能**:序列化器代码会在首次注册 schema 时生成并缓存,而不是每次调用时都重新生成 +- **具备引用感知能力**:启用后可支持共享引用和循环对象图 +- **显式 schema**:字段类型、可空性和多态行为通过 `Type.*` builder 或 TypeScript decorator 一次性声明 +- **安全默认值**:可配置的深度、二进制大小和集合大小限制可以拒绝超出预期的大载荷或深层嵌套载荷 +- **现代类型支持**:支持 `bigint`、typed array、`Map`、`Set`、`Date`、`float16` 和 `bfloat16` + +## 安装 + +从 npm 安装 JavaScript 包: + +```bash +npm install @apache-fory/core +``` + +可选的 Node.js 字符串快速路径支持由 `@apache-fory/hps` 提供: + +```bash +npm install @apache-fory/core @apache-fory/hps +``` + +`@apache-fory/hps` 依赖 Node.js 20+,并且是可选的。如果不可用,Fory 仍可正常工作;只需在配置中省略 `hps`。 + +## 快速开始 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const userType = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + age: Type.int32(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ + id: 1n, + name: "Alice", + age: 30, +}); + +const user = deserialize(bytes); +console.log(user); +// { id: 1n, name: 'Alice', age: 30 } +``` + +## 工作原理 + +Fory 是由 schema 驱动的。你先用 `Type.*` builder 或 TypeScript decorator 描述一次数据结构,然后调用 `fory.register(schema)`。这会返回一个可高频复用、调用开销较低的 `{ serialize, deserialize }` 对。 + +```ts +// 1. 定义 schema +const personType = Type.struct("example.person", { + name: Type.string(), + email: Type.string().setNullable(true), +}); + +// 2. 注册一次 +const fory = new Fory(); +const { serialize, deserialize } = fory.register(personType); + +// 3. 按需重复使用 +const bytes = serialize({ name: "Alice", email: null }); +const person = deserialize(bytes); +``` + +每个应用创建一个 `Fory` 实例并持续复用即可;如果每个请求都新建实例,就会浪费 schema 注册带来的缓存收益。 + +## 配置 + +```ts +import Fory from "@apache-fory/core"; +import hps from "@apache-fory/hps"; + +const fory = new Fory({ + ref: true, + compatible: true, + maxDepth: 100, + maxBinarySize: 64 * 1024 * 1024, + maxCollectionSize: 1_000_000, + hps, +}); +``` + +| 选项 | 默认值 | 说明 | +| -------------------------- | ----------- | ------------------------------------------------------------------------------------ | +| `ref` | `false` | 为共享引用或循环对象图启用引用跟踪 | +| `compatible` | `false` | 允许在不破坏现有消息的前提下新增或删除字段 | +| `maxDepth` | `50` | 最大嵌套深度,必须 `>= 2`。如果结构嵌套很深,可适当调大 | +| `maxBinarySize` | 64 MiB | 任意单个二进制字段可接受的最大字节数 | +| `maxCollectionSize` | `1_000_000` | 任意 list、set 或 map 中可接受的最大元素数 | +| `useSliceString` | `false` | Node.js 下的可选字符串读取优化。除非做过基准测试,否则保持默认即可 | +| `hps` | unset | 来自 `@apache-fory/hps` 的可选快速字符串辅助模块(Node.js 20+) | +| `hooks.afterCodeGenerated` | unset | 用于查看生成后的序列化器代码的回调,对调试很有帮助 | + +## 文档导航 + +| 主题 | 说明 | +| --------------------------------------------- | ------------------------------------------------------ | +| [基础序列化](basic-serialization.md) | 核心 API 与日常用法 | +| [类型注册](type-registration.md) | 数值 ID、名称、decorator 与 schema 注册方式 | +| [支持的类型](supported-types.md) | 原始类型、集合、时间、枚举与 struct 的映射方式 | +| [引用](references.md) | 共享引用与循环对象图 | +| [Schema 演进](schema-evolution.md) | 兼容模式与 struct 演进 | +| [跨语言](cross-language.md) | 互操作指导与类型映射规则 | +| [故障排查](troubleshooting.md) | 常见问题、限制项与调试技巧 | + +## 相关资源 + +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) +- [跨语言类型映射](../../specification/xlang_type_mapping.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/references.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/references.md new file mode 100644 index 000000000..e084bcf2a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/references.md @@ -0,0 +1,111 @@ +--- +title: 引用 +sidebar_position: 50 +id: references +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +默认情况下,Fory 会把每个值都当作独立副本处理。如果同一个对象出现在两个字段中,它会被序列化两次;反序列化后,你得到的也是两个彼此独立的副本。以下情况应启用引用跟踪: + +- 同一个对象实例会在对象图中的多个位置被引用 +- 数据中包含循环结构,例如一个节点指向自己 +- 往返之后必须保留对象身份 + +对于普通的树状数据,应保持引用跟踪关闭,因为它会引入少量额外开销。 + +## 第一步:在 `Fory` 实例上启用引用跟踪 + +```ts +const fory = new Fory({ ref: true }); +``` + +## 第二步:标记可能出现共享引用或循环引用的字段 + +对于每个值可能被共享或形成循环的字段,都需要在字段 schema 上调用 `.setTrackingRef(true)`: + +```ts +const nodeType = Type.struct("example.node", { + value: Type.string(), + next: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); +``` + +全局开关和字段级开关必须**同时**启用。缺少任何一个,值都会被复制,而不是按引用恢复。 + +## 循环自引用示例 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const nodeType = Type.struct("example.node", { + name: Type.string(), + selfRef: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(nodeType); + +const node: any = { name: "root", selfRef: null }; +node.selfRef = node; + +const copy = deserialize(serialize(node)); +console.log(copy.selfRef === copy); // true +``` + +## 共享嵌套引用示例 + +```ts +const innerType = Type.struct(501, { + value: Type.string(), +}); + +const outerType = Type.struct(502, { + left: Type.struct(501).setNullable(true).setTrackingRef(true), + right: Type.struct(501).setNullable(true).setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(outerType); + +const shared = { value: "same-object" }; +const copy = deserialize(serialize({ left: shared, right: shared })); +console.log(copy.left === copy.right); // true +``` + +## 何时启用 + +以下情况建议启用引用跟踪: + +- 同一个对象实例会被多个字段重复引用 +- 你的对象图可能存在环 +- 反序列化后对象身份是否保持一致很重要 + +以下情况建议关闭: + +- 数据是普通树结构 +- 你希望获得最低开销 +- 对象身份并不重要 + +## 跨语言说明 + +引用跟踪是 Fory 二进制协议的一部分,并且可以跨运行时工作。为了让行为一致,两端都必须启用引用跟踪,并把相同字段标记为引用跟踪字段。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/schema-evolution.md new file mode 100644 index 000000000..adbb202b1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/schema-evolution.md @@ -0,0 +1,99 @@ +--- +title: Schema 演进 +sidebar_position: 60 +id: schema_evolution +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Schema 演进允许不同版本的服务安全地交换消息。也就是说,v2 写入端生成的消息,v1 读取端仍然可以理解,反之亦然。 + +## 两种模式 + +- **Schema-consistent 模式**(默认):更紧凑,但双方必须拥有完全相同的 schema。适合所有服务统一升级的场景。 +- **兼容模式**:会写入额外的字段元信息,使读取端能够跳过未知字段并容忍缺失字段。适合独立部署或滚动升级场景。 + +## 启用兼容模式 + +```ts +const fory = new Fory({ compatible: true }); +``` + +以下情况建议使用: + +- 服务会独立发布 schema 变更 +- 较旧的读取端可能会看到较新的载荷 +- 较新的读取端可能会看到字段尚未添加之前产生的旧载荷 + +## 示例 + +写入端 schema: + +```ts +const writerType = Type.struct( + { typeId: 1001 }, + { + name: Type.string(), + age: Type.int32(), + }, +); +``` + +字段更少的读取端 schema: + +```ts +const readerType = Type.struct( + { typeId: 1001 }, + { + name: Type.string(), + }, +); +``` + +启用 `compatible: true` 后,读取端会忽略自己不认识的字段,并为未知字段填充默认值。 + +## 为单个 Struct 关闭演进 + +即使实例启用了 `compatible: true`,你仍然可以为某个特定 struct 关闭演进元信息: + +```ts +const fixedType = Type.struct( + { typeId: 1002, evolving: false }, + { + name: Type.string(), + }, +); +``` + +`evolving: false` 会让该 struct 的消息更小,但**写入端和读取端必须在这个设置上保持一致**。如果一侧以 `evolving: false` 写入,而另一侧按兼容元信息读取,反序列化将失败。 + +## 何时使用每种模式 + +| | Schema-consistent | Compatible | +| ------------------------------- | ----------------- | ------------------- | +| 服务总是同时更新 | ✔ 最佳选择 | 可用,但有浪费 | +| 独立部署 | 会出错 | ✔ 最佳选择 | +| 需要尽可能小的消息 | ✔ | 稍大一些 | +| 滚动升级 | 有风险 | ✔ 安全 | + +## 跨语言要求 + +兼容模式只能帮你处理类型**字段**层面的 schema 差异。对于类型标识本身,你仍然需要在各端保持一致,即相同的数值 ID 或相同的 `namespace + typeName`。参见 [跨语言](cross-language.md)。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/supported-types.md new file mode 100644 index 000000000..0bbbf022f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/supported-types.md @@ -0,0 +1,177 @@ +--- +title: 支持的类型 +sidebar_position: 40 +id: supported_types +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页列出 Fory 支持的 JavaScript 和 TypeScript 类型,并说明在跨语言兼容场景下何时需要对类型选择保持明确和谨慎。 + +## 原始类型与标量类型 + +| JavaScript 值 | Fory schema | 说明 | +| ------------------ | ----------------------------------------------------------------------------------- | ---------------------------------------------- | +| `boolean` | `Type.bool()` | | +| `number` | `Type.int8()` / `Type.int16()` / `Type.int32()` / `Type.float32()` / `Type.float64()` | 选择与对端语言一致的位宽 | +| `bigint` | `Type.int64()` / `Type.varInt64()` / `Type.uint64()` | 64 位整数应使用 `bigint` | +| `string` | `Type.string()` | | +| `Uint8Array` | `Type.binary()` | 二进制 blob | +| `Date` | `Type.timestamp()` | 序列化/反序列化结果均为 `Date` | +| `Date` | `Type.date()` | 只包含日期,不包含时间;反序列化结果为 `Date` | +| duration(毫秒) | `Type.duration()` | 在 JavaScript 中暴露为毫秒数 | +| `number` | `Type.float16()` | 半精度浮点数 | +| `BFloat16` / `number` | `Type.bfloat16()` | 反序列化结果为 `BFloat16` | + +## 整数类型 + +JavaScript 的 `number` 是 64 位浮点数,无法安全表示所有 64 位整数,超过 `Number.MAX_SAFE_INTEGER` 的整数会丢失精度。请使用显式 schema,使其与对端语言期望的位宽一致: + +```ts +Type.int8(); // -128 to 127 +Type.int16(); // -32,768 to 32,767 +Type.int32(); // matches Java int, Go int32, C# int +Type.varInt32(); // variable-length encoding; smaller for small values +Type.int64(); // use with bigint; matches Java long, Go int64 +Type.varInt64(); +Type.sliInt64(); +Type.uint8(); +Type.uint16(); +Type.uint32(); +Type.varUInt32(); +Type.uint64(); // use with bigint +Type.varUInt64(); +Type.taggedUInt64(); +``` + +**经验法则**:凡是在其他语言中映射为 64 位整数的值,在 JavaScript 侧都应使用 `Type.int64()` 或 `Type.uint64()`,并以 `bigint` 形式传入。 + +## 浮点类型 + +```ts +Type.float16(); +Type.float32(); +Type.float64(); +Type.bfloat16(); +``` + +当需要与使用低精度数值格式的运行时或载荷互操作时,`float16` 和 `bfloat16` 会很有用。 + +## 数组与 Typed Array + +### 通用数组 + +```ts +Type.array(Type.string()); +Type.array( + Type.struct("example.item", { + id: Type.int64(), + }), +); +``` + +它们会映射为 JavaScript 数组。 + +## 优化过的数值数组 + +对于数值数组,请使用专门的 typed array schema。它们更紧凑,并且会映射到原生 typed array: + +```ts +Type.boolArray(); // boolean[] in JS +Type.int16Array(); // Int16Array +Type.int32Array(); // Int32Array +Type.int64Array(); // BigInt64Array +Type.float32Array(); // Float32Array +Type.float64Array(); // Float64Array +Type.float16Array(); // number[] +Type.bfloat16Array(); // BFloat16[] +``` + +对于非数值数组或 struct 数组,应改用 `Type.array(elementType)`。 + +## Map 与 Set + +```ts +Type.map(Type.string(), Type.int32()); +Type.set(Type.string()); +``` + +它们会映射为 JavaScript `Map` 和 `Set`。 + +## Struct + +```ts +Type.struct("example.user", { + id: Type.int64(), + name: Type.string(), + tags: Type.array(Type.string()), +}); +``` + +Struct 可以以内联方式声明,也可以通过 decorator 声明,或者嵌套在其他 schema 中。 + +## 枚举 + +```ts +Type.enum("example.color", { + Red: 1, + Green: 2, + Blue: 3, +}); +``` + +Fory 按对象中的 ordinal position 编码枚举值,而不是按它们的取值进行编码。两端都必须以相同顺序声明枚举成员。与其他语言互操作时,必须保证成员顺序一致,而不仅仅是值相同。 + +## 可空字段 + +当字段可能为 `null` 时,请使用 `.setNullable(true)`。 + +```ts +Type.string().setNullable(true); +``` + +## 动态字段 + +当字段在运行时可能承载不同类型的值时,请使用 `Type.any()`。 + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +如果字段类型是已知的,应优先使用显式字段 schema,因为 `Type.any()` 更难在不同语言间保持对齐。 + +## 引用跟踪字段 + +当同一个对象实例可能出现在多个字段中,或者你的对象图存在循环时,应为对应字段单独启用引用跟踪: + +```ts +Type.struct("example.node").setTrackingRef(true).setNullable(true); +``` + +这需要同时配置 `new Fory({ ref: true })`。参见 [引用](references.md)。 + +## 扩展类型 + +对于需要完全自定义编码的类型,可以使用 `Type.ext(...)`,并向 `fory.register(...)` 传入自定义序列化器。这属于高级用法;大多数场景下,标准的 `Type.struct` 已经足够。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [引用](references.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/troubleshooting.md new file mode 100644 index 000000000..a4d83c62b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/troubleshooting.md @@ -0,0 +1,105 @@ +--- +title: 故障排查 +sidebar_position: 90 +id: troubleshooting +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +本页介绍使用 Fory JavaScript 时常见的问题。 + +## 无法反序列化非跨语言载荷 + +Fory JavaScript 运行时只能读取 Fory 的跨语言载荷。如果生产端是 Java 或 Go 服务,并且使用的是语言原生格式,那么 JavaScript 侧无法解码。 + +修复方式:把生产端切换到跨语言模式。Java 请使用 `.withLanguage(Language.XLANG)`,Go 请使用 `WithXlang(true)`。 + +## `maxDepth must be an integer >= 2` + +这表示你传入了无效的 `maxDepth` 值。它必须是大于等于 2 的正整数。 + +```ts +new Fory({ maxDepth: 100 }); +``` + +只有当你的数据确实存在很深的嵌套时,才应提高这个值。 + +## `Binary size ... exceeds maxBinarySize` + +某个二进制字段或整条消息超过了安全限制。如果这个大小符合预期,且数据源可信,可以提高限制: + +```ts +new Fory({ maxBinarySize: 128 * 1024 * 1024 }); +``` + +## `Collection size ... exceeds maxCollectionSize` + +某个 list、set 或 map 的元素数量超过了配置上限。这通常意味着数据异常地大。如果这是合法场景,可以提高限制: + +```ts +new Fory({ maxCollectionSize: 2_000_000 }); +``` + +## `Field "..." is not nullable` + +你向一个未声明为可空的字段传入了 `null`。修复方式:在字段 schema 上添加 `.setNullable(true)`: + +```ts +const userType = Type.struct("example.user", { + name: Type.string(), + email: Type.string().setNullable(true), // ← this field can be null +}); +``` + +## 反序列化后对象不是同一个实例 + +默认情况下,Fory 不保留对象身份。两个字段如果指向同一个对象,反序列化后会变成两个彼此独立的副本。 + +修复方式:同时启用以下**两个**开关: + +1. 在实例上配置 `new Fory({ ref: true })` +2. 在具体字段上调用 `.setTrackingRef(true)` + +参见 [引用](references.md)。 + +## 大整数会以 `bigint` 返回 + +这是预期行为。对于任何 64 位整数字段,例如 `Type.int64()`、`Type.uint64()`,Fory 都会使用 `bigint`。如果你确实需要 `number`,请使用更小的整数类型,比如 `Type.int32()`,但前提是该值确实能装进 32 位。 + +## 查看生成的序列化器代码 + +如果你需要排查 Fory 在底层生成了什么,可以通过 hook 查看生成后的序列化器代码: + +```ts +const fory = new Fory({ + hooks: { + afterCodeGenerated(code) { + console.log(code); + return code; + }, + }, +}); +``` + +## `@apache-fory/hps` 安装失败 + +`@apache-fory/hps` 是一个可选的 Node.js 加速模块。如果它安装失败,例如当前平台不支持原生模块,只需把它从依赖中移除即可。即使没有它,Fory 也能正常工作。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [引用](references.md) +- [跨语言](cross-language.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/type-registration.md new file mode 100644 index 000000000..5fa242e88 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/type-registration.md @@ -0,0 +1,200 @@ +--- +title: 类型注册 +sidebar_position: 30 +id: type_registration +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +你要序列化的每个 struct 和 enum,在使用前都必须先注册到 `Fory` 实例中。注册会告诉 Fory:如何在消息中标识该类型,以及如何对其进行编码和解码。 + +## 注册 Struct + +你可以使用数值 ID 或名称来标识一个 struct。选择一种策略,并在所有共享这类消息的语言中保持一致。 + +### 按数值 ID 注册 + +编码更紧凑。当团队规模较小、可以协调 ID 分配时,这是很好的选择。 + +```ts +const userType = Type.struct( + { typeId: 1001 }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); +``` + +所有读写该类型的运行时都必须使用同一个数值。 + +### 按名称注册 + +更容易跨团队协同,但消息中的元信息会稍大一些。 + +```ts +const userType = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); +``` + +你也可以显式拆分 `namespace` 和类型名: + +```ts +const userType = Type.struct( + { namespace: "example", typeName: "user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); +``` + +> **同一个类型不要在不同运行时中混用两种策略。** 如果一侧使用数值 ID,另一侧使用名称,反序列化会失败。 + +## 使用 Decorator 注册 + +```ts [email protected]({ typeId: 1001 }) +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(User); +``` + +当你希望 TypeScript 类声明与 schema 定义放在一起时,基于 decorator 的注册会很方便。 + +## 注册 Enum + +Fory JavaScript 同时支持普通 JavaScript 风格的枚举对象和 TypeScript enum。 + +### JavaScript 对象枚举 + +```ts +const Color = { + Red: 1, + Green: 2, + Blue: 3, +}; + +const fory = new Fory(); +const colorSerde = fory.register(Type.enum("example.color", Color)); +``` + +### TypeScript enum + +```ts +enum Status { + Pending = "pending", + Active = "active", +} + +const fory = new Fory(); +fory.register(Type.enum("example.status", Status)); +``` + +## 注册作用域 + +注册是以 `Fory` 实例为作用域的。如果你创建了两个实例,就需要在两个实例中都注册 schema。 + +## `register` 的返回值 + +`fory.register(schema)` 会返回一个绑定后的序列化器对: + +```ts +const { serialize, deserialize } = fory.register(orderType); + +// serialize returns Uint8Array bytes +const bytes = serialize({ id: 1n, total: 99.99 }); + +// deserialize returns the decoded value +const order = deserialize(bytes); +``` + +把这个返回对保存起来并重复复用,它就是性能最优的调用路径。 + +## 字段选项 + +### 可空字段 + +如果字段可能为 `null`,请显式标记。向不可空字段传入 `null` 会抛出异常。 + +```ts +Type.string().setNullable(true); +``` + +### 字段上的引用跟踪 + +当同一个对象实例可能出现在多个字段中时,需要启用字段级引用跟踪,详见 [引用](references.md): + +```ts +Type.struct("example.node").setTrackingRef(true); +``` + +只有在同时设置了 `new Fory({ ref: true })` 时,这个选项才会生效。 + +### 多态字段 + +当字段在运行时可能承载不同类型的值时,可以使用 `Type.any()`: + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +如果你需要更细粒度地控制某个 struct 字段如何处理运行时类型,可以调用 `.setDynamic(Dynamic.FALSE)`,表示始终按声明类型处理;或者调用 `.setDynamic(Dynamic.TRUE)`,表示始终写入运行时类型。默认值 `Dynamic.AUTO` 适用于绝大多数场景。 + +## 如何选择 ID 与名称 + +以下情况适合使用**数值 ID**: + +- 你希望消息尽可能小 +- 你的组织能够保证 ID 稳定且全局唯一 +- 服务之间协同非常紧密 + +以下情况适合使用**名称**: + +- 不同团队独立定义类型 +- schema 本身已经通过 package/module name 标识 +- 可以接受稍大的元信息开销 + +## 跨语言 + +如果要让消息在 JavaScript 与其他运行时之间往返,双方必须对某个类型使用相同的类型标识:相同的数值 ID,或相同的 `namespace + typeName`。参见 [跨语言](cross-language.md)。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](cross-language.md) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
