7fb9031f8d
CI / flutter-gates (push) Successful in 1m12s
- pet_models.dart:pets 域 DTO 逐字段对齐冻结契约(宠物/品种/体重/ 疫苗目录/疫苗/健康事件/照护提醒/聚合摘要 + cursor 分页信封), 枚举严格解析,请求体部分更新语义(缺席字段不出现) - pets_repository.dart:PetsRepository 抽象 + ApiPetsRepository, 12 路径 18 操作全覆盖;四个 POST 携带 Idempotency-Key; 复用既有 ApiClient token 拦截与单飞刷新重放 - pet_exceptions.dart:新 8 错误码映射为类型化异常 (40300/40401/40402/40902/40903/40904/42201/42202), 仍可按 ApiBusinessException 基类捕获 - pets_controller.dart:宠物档案状态自 AppState 拆出 (Controller → Repository → API Client 分层,四态就绪供 T2-12) - money.dart:元/分换算工具(DTO 层保持整数分,UI 层 T2-14 使用) - api_client.dart:新增 patbondPetApiBaseUrl(:8083,--dart-define 可覆盖)与 query 参数支持;api_exception.dart 补 pets 域错误码 - 测试 64 → 126:DTO 映射、错误类型化映射、请求线路 (路径/方法/鉴权/分页/幂等键/tz)、控制器状态机、金额换算 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
90 lines
3.3 KiB
Dart
90 lines
3.3 KiB
Dart
/// 服务端错误信封 `{code, message, data}` 的业务码常量(接口契约冻结稿)。
|
||
abstract final class ApiCodes {
|
||
static const ok = 0;
|
||
|
||
/// 参数错误。
|
||
static const paramError = 40000;
|
||
|
||
/// 用户名或密码错误。
|
||
static const badCredentials = 40100;
|
||
|
||
/// access token 无效或过期。
|
||
static const accessTokenInvalid = 40101;
|
||
|
||
/// refresh token 已失效。
|
||
static const refreshTokenInvalid = 40102;
|
||
|
||
/// 用户名重复。
|
||
static const usernameTaken = 40900;
|
||
|
||
/// 手机号重复。
|
||
static const phoneTaken = 40901;
|
||
|
||
/// 登录失败次数过多,账号临时锁定(HTTP 423,见 openapi.yaml)。
|
||
static const loginLocked = 42300;
|
||
|
||
// ------ pets 域(契约 v1.2.0 冻结,M2 第二波定型 8 个)------
|
||
|
||
/// 对可见宠物无相应操作权限(viewer 写记录、caregiver/viewer 改档案)。
|
||
static const petAccessDenied = 40300;
|
||
|
||
/// 宠物不存在 / 已软删 / 与调用者无关系(防枚举,三种情况响应一致)。
|
||
static const petNotFound = 40401;
|
||
|
||
/// 记录不存在或记录所属宠物对调用者不可见(记录级防枚举)。
|
||
static const recordNotFound = 40402;
|
||
|
||
/// 乐观锁版本冲突(提交的 version 已过期;提醒的条件更新守卫落空同码)。
|
||
static const versionConflict = 40902;
|
||
|
||
/// 芯片号已被登记(跨用户唯一)。
|
||
static const microchipTaken = 40903;
|
||
|
||
/// 同宠物同疫苗同系列同剂次的非 cancelled 疫苗记录已存在。
|
||
static const vaccinationDoseExists = 40904;
|
||
|
||
/// 疫苗状态机 / 状态-日期规则违反(HTTP 422)。
|
||
static const vaccinationRuleViolation = 42201;
|
||
|
||
/// 提醒状态机 / completed-completedAt 一致性违反(HTTP 422)。
|
||
static const careReminderRuleViolation = 42202;
|
||
}
|
||
|
||
/// API 调用的类型化异常。页面按类型映射为三层错误呈现
|
||
/// (字段级 / 表单横幅 / SnackBar,见 12 号组装稿 §4),
|
||
/// 服务端原始 message 一律不直接透出给用户。
|
||
sealed class ApiException implements Exception {
|
||
const ApiException(this.message);
|
||
|
||
/// 服务端返回的原始 message(仅用于日志排查,不上屏)。
|
||
final String message;
|
||
|
||
@override
|
||
String toString() => '$runtimeType: $message';
|
||
}
|
||
|
||
/// 系统错误:超时、断网、5xx、响应无法解析。归瞬态层(SnackBar + 重试)。
|
||
final class ApiNetworkException extends ApiException {
|
||
const ApiNetworkException([super.message = '网络或服务不可用']);
|
||
}
|
||
|
||
/// 业务错误:错误信封 code != 0(40000/40100/40900/40901 等)。
|
||
/// 领域层可按错误码细分子类型(见 features/pets/pet_exceptions.dart)。
|
||
class ApiBusinessException extends ApiException {
|
||
const ApiBusinessException({required this.code, required String message})
|
||
: super(message);
|
||
|
||
final int code;
|
||
}
|
||
|
||
/// 限流(HTTP 429):映射为横幅「尝试次数过多,请稍后再试」。
|
||
final class ApiRateLimitException extends ApiException {
|
||
const ApiRateLimitException([super.message = '请求过于频繁']);
|
||
}
|
||
|
||
/// 会话已失效:refresh token 失效(40102)或刷新重放后仍 401。
|
||
/// 抛出前本地会话已被清除,应用会自动回到登录页。
|
||
final class SessionExpiredException extends ApiException {
|
||
const SessionExpiredException([super.message = '会话已失效']);
|
||
}
|