Template
chore: 初始化脚手架 — sunny_mochi 企业级 Flutter 模板
Core 基础设施:Flavor 三包体系、Drift+SQLCipher 加密数据库 7 拦截器 Dio 网络栈、CrashReporter 崩溃日志、Sealed Failure 错误体系 5 色板主题系统、Auth 认证骨架(Clean Architecture)、Dev Panel、Mock Adapter 通过验证:flutter analyze(0 errors)、flutter test(全绿)、staging APK 74.8MB
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import 'package:sunny_mochi/core/error/exception_mapper.dart'
|
||||
show ExceptionMapper;
|
||||
|
||||
/// 应用层统一失败模型。
|
||||
///
|
||||
/// 所有数据/网络/缓存层的异常通过 [ExceptionMapper] 转换为 [Failure] 子类,
|
||||
/// presentation 层只面对 Failure,避免散落 try-catch DioException / DriftException。
|
||||
///
|
||||
/// **实现 [Exception]**:让 datasource/repository 可以直接 `throw failure;`
|
||||
/// 不触发 `only_throw_errors` lint。
|
||||
///
|
||||
/// 详见 docs/flutter-architecture-design.md §四.2。
|
||||
sealed class Failure implements Exception {
|
||||
const Failure({required this.message, this.cause, this.stackTrace});
|
||||
|
||||
final String message;
|
||||
final Object? cause;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
/// 子类自我描述前缀(避免直接用 [Object.runtimeType],Release 混淆友好)。
|
||||
String get _typeName;
|
||||
|
||||
/// 适合展示给用户的文案(区别于 [message],后者含技术细节,供 Sentry/Talker 日志使用)。
|
||||
String get userMessage;
|
||||
|
||||
@override
|
||||
String toString() => '$_typeName: $message';
|
||||
}
|
||||
|
||||
/// 网络层失败:超时 / 无网 / DNS 解析失败 / 证书绑定失败 / 连接断开。
|
||||
class NetworkFailure extends Failure {
|
||||
const NetworkFailure({
|
||||
required super.message,
|
||||
this.kind = NetworkFailureKind.unknown,
|
||||
super.cause,
|
||||
super.stackTrace,
|
||||
});
|
||||
|
||||
final NetworkFailureKind kind;
|
||||
|
||||
@override
|
||||
String get _typeName => 'NetworkFailure';
|
||||
|
||||
@override
|
||||
String get userMessage => message;
|
||||
}
|
||||
|
||||
enum NetworkFailureKind {
|
||||
timeout,
|
||||
noNetwork,
|
||||
badCertificate,
|
||||
cancelled,
|
||||
unknown,
|
||||
}
|
||||
|
||||
/// 鉴权失败:未登录 / Token 过期 / refresh 失败 → 应跳登录。
|
||||
class AuthFailure extends Failure {
|
||||
const AuthFailure({
|
||||
required super.message,
|
||||
this.kind = AuthFailureKind.unauthorized,
|
||||
super.cause,
|
||||
super.stackTrace,
|
||||
});
|
||||
|
||||
final AuthFailureKind kind;
|
||||
|
||||
@override
|
||||
String get _typeName => 'AuthFailure';
|
||||
|
||||
@override
|
||||
String get userMessage => message;
|
||||
}
|
||||
|
||||
enum AuthFailureKind {
|
||||
unauthorized, // A0401
|
||||
forbidden, // 403
|
||||
refreshFailed,
|
||||
cryptoFailed, // 客户端加密失败(如登录密码 RSA 加密前置错)
|
||||
}
|
||||
|
||||
/// 服务端业务失败:500/501 / 业务校验失败 / 参数错误。
|
||||
class ServerFailure extends Failure {
|
||||
const ServerFailure({
|
||||
required super.message,
|
||||
this.code,
|
||||
this.statusCode,
|
||||
super.cause,
|
||||
super.stackTrace,
|
||||
});
|
||||
|
||||
final String? code; // 业务错误码(如 A0500 / A0400 / A0404)
|
||||
final int? statusCode; // HTTP 状态码
|
||||
|
||||
@override
|
||||
String get _typeName => 'ServerFailure';
|
||||
|
||||
@override
|
||||
String get userMessage {
|
||||
// HTTP 5xx:服务端内部错误,后端 message 可能含技术细节(SQL/堆栈),统一遮蔽
|
||||
if (statusCode != null && statusCode! >= 500) return '服务器开小差了,请稍后再试';
|
||||
// 业务码 / 4xx:message 已经过 ResponseCode.friendlyMessage 映射,安全展示
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/// 本地缓存/数据库失败:Drift 异常 / SQLCipher 解密失败 / 文件 IO 错误。
|
||||
class CacheFailure extends Failure {
|
||||
const CacheFailure({
|
||||
required super.message,
|
||||
super.cause,
|
||||
super.stackTrace,
|
||||
});
|
||||
|
||||
@override
|
||||
String get _typeName => 'CacheFailure';
|
||||
|
||||
@override
|
||||
String get userMessage => '本地数据异常,请重启应用试试';
|
||||
}
|
||||
|
||||
/// 兜底未知错误。
|
||||
class UnknownFailure extends Failure {
|
||||
const UnknownFailure({
|
||||
required super.message,
|
||||
super.cause,
|
||||
super.stackTrace,
|
||||
});
|
||||
|
||||
@override
|
||||
String get _typeName => 'UnknownFailure';
|
||||
|
||||
@override
|
||||
String get userMessage => '遇到了点问题,已自动上报';
|
||||
}
|
||||
Reference in New Issue
Block a user