da25804a06
- 对齐后端 T4 新增的登录锁定契约(openapi.yaml),锁定时展示明确横幅而非通用兜底 - 门禁:format 0 changed / analyze 0 issues / flutter test 30 passed
63 lines
2.1 KiB
Dart
63 lines
2.1 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;
|
||
}
|
||
|
||
/// 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 等)。
|
||
final 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 = '会话已失效']);
|
||
}
|