Files
lixi 19bd8c1810
CI / flutter-gates (push) Successful in 2m26s
新增:community feature 数据层——契约 v1.3.0 十九操作全覆盖 + ToggleSync 乐观更新状态机(T3-12)
- 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>
2026-09-09 12:01:25 +08:00

103 lines
4.5 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:patbond_flutter/core/network/api_exception.dart';
/// community / media 域类型化业务异常(契约 v1.3.0 定型的 9 个新错误码,
/// 20 号收口报告 §140902 乐观锁与 pets 域共码,本域映射为独立类型)。
/// 全部继承 [ApiBusinessException],既有按基类捕获的通用错误处理不受影响。
/// 40301:对可见帖子/评论无相应操作权限(改删他人已发布帖、
/// 删他人可见评论——含帖主删他人评论,D3-7 首版不做)。
final class PostAccessDeniedException extends ApiBusinessException {
const PostAccessDeniedException({required super.message})
: super(code: ApiCodes.postAccessDenied);
}
/// 40403:帖子不存在 / 已软删 / hidden/archived(含作者)/ 他人 draft
/// (防枚举,全部情况响应完全一致;互动路径上含作者本人草稿)。
final class PostNotFoundException extends ApiBusinessException {
const PostNotFoundException({required super.message})
: super(code: ApiCodes.postNotFound);
}
/// 40404:评论不存在、已删或所属帖子不可见(防枚举合并)。
final class CommentNotFoundException extends ApiBusinessException {
const CommentNotFoundException({required super.message})
: super(code: ApiCodes.commentNotFound);
}
/// 40405media asset 不存在、非本人所有或已删(防枚举合并)。
final class MediaAssetNotFoundException extends ApiBusinessException {
const MediaAssetNotFoundException({required super.message})
: super(code: ApiCodes.mediaNotFound);
}
/// 40406:目标用户不存在或已注销(合并不泄露成因)。
final class CommunityUserNotFoundException extends ApiBusinessException {
const CommunityUserNotFoundException({required super.message})
: super(code: ApiCodes.communityUserNotFound);
}
/// 40902:帖子编辑乐观锁版本冲突(version 过期)。
/// 客户端处理:刷新详情取新 version 后重提。
final class PostVersionConflictException extends ApiBusinessException {
const PostVersionConflictException({required super.message})
: super(code: ApiCodes.versionConflict);
}
/// 40905:同 Idempotency-Key 不同 payload(规范化 request_hash 不符)。
/// 客户端每次逻辑提交应换新键,重试间保持不变。
final class IdempotencyMismatchException extends ApiBusinessException {
const IdempotencyMismatchException({required super.message})
: super(code: ApiCodes.idempotencyKeyMismatch);
}
/// 42203:引用了本人所有但非 readyuploading/failed)状态的 asset。
final class MediaNotReadyException extends ApiBusinessException {
const MediaNotReadyException({required super.message})
: super(code: ApiCodes.mediaNotReady);
}
/// 42204:自关注(仅 PUT;自取关走 DELETE 的 200 幂等 no-op)。
final class SelfFollowException extends ApiBusinessException {
const SelfFollowException({required super.message})
: super(code: ApiCodes.selfFollow);
}
/// 42205:上传状态不允许确认——对象未上传(保持 uploading 可重试)、
/// 大小/类型不符(置 failed 终态)、failed 态再确认(已 ready 幂等 200 除外)。
final class MediaUploadStateException extends ApiBusinessException {
const MediaUploadStateException({required super.message})
: super(code: ApiCodes.mediaUploadStateInvalid);
}
/// 把通用业务异常按 community/media 域错误码升格为类型化异常;
/// 未覆盖的码(40000 参数错误、40401 宠物防枚举等)原样返回,沿用通用处理。
ApiBusinessException mapCommunityBusinessException(ApiBusinessException error) {
return switch (error.code) {
ApiCodes.postAccessDenied => PostAccessDeniedException(
message: error.message,
),
ApiCodes.postNotFound => PostNotFoundException(message: error.message),
ApiCodes.commentNotFound => CommentNotFoundException(
message: error.message,
),
ApiCodes.mediaNotFound => MediaAssetNotFoundException(
message: error.message,
),
ApiCodes.communityUserNotFound => CommunityUserNotFoundException(
message: error.message,
),
ApiCodes.versionConflict => PostVersionConflictException(
message: error.message,
),
ApiCodes.idempotencyKeyMismatch => IdempotencyMismatchException(
message: error.message,
),
ApiCodes.mediaNotReady => MediaNotReadyException(message: error.message),
ApiCodes.selfFollow => SelfFollowException(message: error.message),
ApiCodes.mediaUploadStateInvalid => MediaUploadStateException(
message: error.message,
),
_ => error,
};
}