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:
SkyJourney
2026-05-14 12:51:05 +08:00
commit 61017f1c39
204 changed files with 15386 additions and 0 deletions
@@ -0,0 +1,180 @@
import 'dart:async';
import 'package:drift/drift.dart' show Value;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sunny_mochi/core/config/env.dart';
import 'package:sunny_mochi/core/crypto/rsa_helper.dart';
import 'package:sunny_mochi/core/error/failures.dart';
import 'package:sunny_mochi/core/storage/app_database.dart';
import 'package:sunny_mochi/core/storage/daos/users_dao.dart';
import 'package:sunny_mochi/core/storage/secure_storage.dart';
import 'package:sunny_mochi/features/auth/data/datasources/auth_remote_datasource.dart';
import 'package:sunny_mochi/features/auth/data/models/user_model.dart';
import 'package:sunny_mochi/features/auth/domain/entities/user_entity.dart';
import 'package:sunny_mochi/features/auth/domain/repositories/auth_repository.dart';
part 'auth_repository_impl.g.dart';
@riverpod
AuthRepository authRepository(Ref ref) => AuthRepositoryImpl(
ref.watch(authRemoteDatasourceProvider),
ref.watch(secureStorageProvider),
usersDaoFactory: () => ref.read(usersDaoProvider),
);
class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl(
this._remote,
this._storage, {
String? rsaPublicKey,
UsersDao Function()? usersDaoFactory,
}) : _rsaPublicKey = rsaPublicKey ?? Env.rsaPublicKey,
_usersDaoFactory = usersDaoFactory;
final AuthRemoteDatasource _remote;
final SecureStorage _storage;
final String _rsaPublicKey;
final UsersDao Function()? _usersDaoFactory;
@override
Future<UserEntity> loginWithPhone({
required String phone,
required String code,
}) async {
final model = await _remote.loginWithPhone(phone, code);
await _persistUser(model);
return model.toEntity();
}
@override
Future<UserEntity> loginWithPassword({
required String phone,
required String password,
}) async {
final String encrypted;
try {
encrypted = RsaHelper.encrypt(password, publicKeyDerBase64: _rsaPublicKey);
} on RsaEncryptionException catch (e, st) {
throw AuthFailure(
message: '密码加密失败,请重试',
kind: AuthFailureKind.cryptoFailed,
cause: e,
stackTrace: st,
);
}
final model = await _remote.loginWithPassword(phone, encrypted);
await _persistUser(model);
return model.toEntity();
}
@override
Future<void> logout() async {
final userId = await _storage.getUserId();
await _storage.clearAll();
unawaited(_backgroundLogoutCleanup(userId));
}
Future<void> _backgroundLogoutCleanup(String? userId) async {
try {
await _remote.logout();
} on Object {
// best-effort
}
if (userId != null && _usersDaoFactory != null) {
try {
await _usersDaoFactory().deleteById(userId);
} on Object {
// mock/测试场景静默
}
}
}
@override
Future<UserEntity?> getCurrentUser() async {
final token = await _storage.getToken();
if (token == null) return null;
final userId = await _storage.getUserId();
if (userId != null && _usersDaoFactory != null) {
try {
final row = await _usersDaoFactory().getById(userId);
if (row != null) {
return UserEntity(
userId: userId,
token: token,
username: row.username,
realName: row.realName,
avatar: row.avatar,
phone: row.phone,
employeeNo: row.employeeNo,
company: row.company,
department: row.department,
);
}
} on Object {
// Drift 不可用时降级
}
}
return UserEntity(userId: userId, token: token);
}
@override
Future<String?> sendSmsCode(String phone) => _remote.sendSmsCode(phone);
@override
Future<UserEntity> fetchUserProfile() async {
final model = await _remote.getUserProfile();
final userId = await _storage.getUserId();
if (userId != null && _usersDaoFactory != null) {
try {
await _usersDaoFactory().upsert(
UsersCompanion(
userId: Value(userId),
username: Value(model.username),
realName: Value(model.realName),
phone: Value(model.phone),
avatar: Value(model.avatar),
email: Value(model.email),
gender: Value(model.gender),
employeeNo: Value(model.employeeNo),
company: Value(model.company),
department: Value(model.department),
),
);
} on Object {
// dao 不可用静默
}
}
return model.toEntity();
}
Future<void> _persistUser(UserModel model) async {
if (model.token != null) await _storage.setToken(model.token!);
if (model.tokenName != null && model.tokenName!.isNotEmpty) {
await _storage.setTokenName(model.tokenName!);
}
if (model.refreshToken != null) {
await _storage.setRefreshToken(model.refreshToken!);
}
if (model.userId != null) await _storage.setUserId(model.userId!);
final userId = model.userId;
if (userId != null && _usersDaoFactory != null) {
try {
await _usersDaoFactory().upsert(
UsersCompanion(
userId: Value(userId),
username: Value(model.username),
realName: Value(model.realName),
phone: Value(model.phone),
avatar: Value(model.avatar),
email: Value(model.email),
birthday: Value(model.birthday),
gender: Value(model.gender),
refreshToken: Value(model.refreshToken),
),
);
} on Object {
// dao 不可用静默
}
}
}
}
@@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_repository_impl.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(authRepository)
final authRepositoryProvider = AuthRepositoryProvider._();
final class AuthRepositoryProvider
extends $FunctionalProvider<AuthRepository, AuthRepository, AuthRepository>
with $Provider<AuthRepository> {
AuthRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'authRepositoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$authRepositoryHash();
@$internal
@override
$ProviderElement<AuthRepository> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
AuthRepository create(Ref ref) {
return authRepository(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AuthRepository value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AuthRepository>(value),
);
}
}
String _$authRepositoryHash() => r'3a8f259bef644802bc3ef4169a8456a3a05aaea8';