新增:community feature 数据层——契约 v1.3.0 十九操作全覆盖 + ToggleSync 乐观更新状态机(T3-12)
CI / flutter-gates (push) Successful in 2m26s

- community_models:community/media 域 DTO 逐字段照冻结契约手写 JSON 映射
  (Post/FeedCard/PostComment/AuthorSummary 降级形态/媒体两步上传凭据等),
  未知枚举抛 FormatException 暴露契约漂移;CursorPage 上移 core 复用
- community_repository:13 路径 19 操作全覆盖;createPost/createComment
  必带 Idempotency-Key(每次逻辑提交换新键、刷新重放同键);点赞/收藏/
  关注走 PUT/DELETE 语义幂等
- community_exceptions:v1.3.0 新增 9 码 + 40902 共码类型化异常映射
- toggle_sync:乐观翻转 + 快照回滚 + 单飞合并最终意图 + 代次守卫,
  点赞/收藏共用一套参数化状态机,权威终态对账收敛
- community_controller:Feed 多页缓存 + 首屏四态 + 尾部加载三态 +
  游标拼接 + 刷新代次丢弃旧尾页;详情副本与卡片互动状态同源;reset 清态
- app.dart 装配:community 服务分端口直连(:8084),共享 TokenRefresher,
  登出同步 reset
- 测试 286 → 347(模型映射 / 19 操作线路 / 错误映射 / 竞态序列全覆盖)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 12:01:25 +08:00
parent 66f983d680
commit 19bd8c1810
14 changed files with 2867 additions and 28 deletions
+27
View File
@@ -0,0 +1,27 @@
/// cursor 分页正典信封 `{items, nextCursor, hasMore}`pets 域体重 / 健康事件,
/// community 域 Feed / 我的帖子 / 评论 / 收藏共用;自 pet_models.dart 上移至 core)。
class CursorPage<T> {
const CursorPage({
required this.items,
required this.nextCursor,
required this.hasMore,
});
factory CursorPage.fromJson(
Map<String, dynamic> json,
T Function(Map<String, dynamic>) itemFromJson,
) {
return CursorPage(
items: (json['items'] as List)
.map((item) => itemFromJson(item as Map<String, dynamic>))
.toList(),
// 不透明 base64url 游标,客户端不得解析;hasMore=false 时恒为 null。
nextCursor: json['nextCursor'] as String?,
hasMore: json['hasMore'] as bool,
);
}
final List<T> items;
final String? nextCursor;
final bool hasMore;
}
+8
View File
@@ -25,6 +25,14 @@ const String patbondPetApiBaseUrl = String.fromEnvironment(
defaultValue: 'http://127.0.0.1:8083',
);
/// community 服务基地址(/api/v1/posts、/api/v1/feed、/api/v1/media 等
/// community/media 域 13 路径):沿用分端口直连模式,
/// `--dart-define=PATBOND_COMMUNITY_API_BASE_URL=...` 注入。
const String patbondCommunityApiBaseUrl = String.fromEnvironment(
'PATBOND_COMMUNITY_API_BASE_URL',
defaultValue: 'http://127.0.0.1:8084',
);
/// 构建全局共用的 Dio 实例。
///
/// `validateStatus` 放行所有状态码:错误信封由 [ApiClient] 统一解析成
+30
View File
@@ -48,6 +48,36 @@ abstract final class ApiCodes {
/// 提醒状态机 / completed-completedAt 一致性违反(HTTP 422)。
static const careReminderRuleViolation = 42202;
// ------ community / media 域(契约 v1.3.0 冻结,M3 第二波定型 9 个)------
/// 对可见帖子/评论无相应操作权限(改删他人已发布帖、删他人可见评论)。
static const postAccessDenied = 40301;
/// 帖子不存在 / 已软删 / hidden/archived(含作者)/ 他人 draft
/// (防枚举,全部情况响应一致;互动路径上含作者本人草稿)。
static const postNotFound = 40403;
/// 评论不存在、已删或所属帖子不可见(防枚举合并)。
static const commentNotFound = 40404;
/// media asset 不存在、非本人所有或已删(防枚举合并)。
static const mediaNotFound = 40405;
/// 目标用户不存在或已注销(合并不泄露成因)。
static const communityUserNotFound = 40406;
/// 同 Idempotency-Key 不同 payload(规范化 request_hash 不符)。
static const idempotencyKeyMismatch = 40905;
/// 引用了本人所有但非 readyuploading/failed)状态的 assetHTTP 422)。
static const mediaNotReady = 42203;
/// 自关注(仅 PUT;自取关走 DELETE 的 200 幂等 no-op)。
static const selfFollow = 42204;
/// 上传状态不允许确认(非 uploading 态或对象校验未通过,HTTP 422)。
static const mediaUploadStateInvalid = 42205;
}
/// API 调用的类型化异常。页面按类型映射为三层错误呈现