Merge pull request '更新数据库' (#1) from happi/dev/database-update into dev
Reviewed-on: https://gitea.winwayinfo.com/CUS-IM/customer-im-client/pulls/1
This commit is contained in:
@@ -11,14 +11,22 @@ import '../../data/local/drift/app_database.dart';
|
|||||||
/// 用法:
|
/// 用法:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// // 登录后开库
|
/// // 登录后开库
|
||||||
/// await ref.read(storageSdkProvider).openDatabase(user.id);
|
/// await ref.read(storageSdkLifecycleProvider).openDatabase(user.id);
|
||||||
///
|
///
|
||||||
/// // CRUD 示例
|
/// // CRUD 示例
|
||||||
/// final db = ref.read(storageSdkProvider);
|
/// final db = ref.read(storageSdkProvider);
|
||||||
/// await db.insertOrReplace(appDb.users, companion);
|
/// await db.insertOrReplace<UsersCompanion>(companion);
|
||||||
|
/// final users = await db.selectAll<User>();
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
final storageSdkProvider = Provider<StorageSdkApi>((ref) {
|
final storageSdkProvider = Provider<StorageSdkApi>((ref) {
|
||||||
return StorageSdkApi(
|
return StorageSdkApi(
|
||||||
databaseFactory: (executor) => AppDatabase(executor),
|
databaseFactory: (executor) => AppDatabase(executor),
|
||||||
|
tableRegistry: (db) => AppDatabase.getTableRegistry(db),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// 生命周期管理,仅供登录/登出使用。
|
||||||
|
final storageSdkLifecycleProvider = Provider<StorageSdkLifecycle>((ref) {
|
||||||
|
return ref.read(storageSdkProvider) as StorageSdkLifecycle;
|
||||||
|
});
|
||||||
@@ -64,6 +64,7 @@ enum AppRouteName {
|
|||||||
chatDetail('/chat/detail'),
|
chatDetail('/chat/detail'),
|
||||||
// 路径参数形式:导航用 AppRouteName.chatDetailByIdPath(id),不直接用 .path
|
// 路径参数形式:导航用 AppRouteName.chatDetailByIdPath(id),不直接用 .path
|
||||||
chatDetailById('/chat/:id'),
|
chatDetailById('/chat/:id'),
|
||||||
|
chatDBTest('/chat/dbTest'),
|
||||||
|
|
||||||
// ── Settings 子路由 ───────────────────────────────────────────────────────
|
// ── Settings 子路由 ───────────────────────────────────────────────────────
|
||||||
settingsTheme('/settings/theme'),
|
settingsTheme('/settings/theme'),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:im_app/features/chat/view/chat_db_test_page.dart';
|
||||||
|
|
||||||
import '../../features/app_tab/view/app_tab.dart';
|
import '../../features/app_tab/view/app_tab.dart';
|
||||||
import '../../features/chat/view/chat_detail_page.dart';
|
import '../../features/chat/view/chat_detail_page.dart';
|
||||||
@@ -115,6 +116,13 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
// parentNavigatorKey: _rootKey 确保路由覆盖 Shell,TabBar 消失
|
// parentNavigatorKey: _rootKey 确保路由覆盖 Shell,TabBar 消失
|
||||||
//
|
//
|
||||||
// extra 传参:接收 ({String conversationId, String title})
|
// extra 传参:接收 ({String conversationId, String title})
|
||||||
|
GoRoute(
|
||||||
|
parentNavigatorKey: _rootKey,
|
||||||
|
path: AppRouteName.chatDBTest.path,
|
||||||
|
builder: (context, state) {
|
||||||
|
return const ChatDbTestPage();
|
||||||
|
},
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
parentNavigatorKey: _rootKey,
|
parentNavigatorKey: _rootKey,
|
||||||
path: AppRouteName.chatDetail.path,
|
path: AppRouteName.chatDetail.path,
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ String? authGuard(AuthNotifier authNotifier, GoRouterState state) {
|
|||||||
case AppRouteName.login:
|
case AppRouteName.login:
|
||||||
// 已登录还在登录页 → 跳聊天页
|
// 已登录还在登录页 → 跳聊天页
|
||||||
return isLoggedIn ? AppRouteName.chat.path : null;
|
return isLoggedIn ? AppRouteName.chat.path : null;
|
||||||
|
|
||||||
case AppRouteName.chat:
|
case AppRouteName.chat:
|
||||||
case AppRouteName.chatDetail:
|
case AppRouteName.chatDetail:
|
||||||
case AppRouteName.chatDetailById:
|
case AppRouteName.chatDetailById:
|
||||||
case AppRouteName.contact:
|
case AppRouteName.contact:
|
||||||
case AppRouteName.settings:
|
case AppRouteName.settings:
|
||||||
case AppRouteName.settingsTheme:
|
case AppRouteName.settingsTheme:
|
||||||
|
case AppRouteName.chatDBTest:
|
||||||
// 受保护路由 → 未登录跳登录页
|
// 受保护路由 → 未登录跳登录页
|
||||||
return isLoggedIn ? null : AppRouteName.login.path;
|
return isLoggedIn ? null : AppRouteName.login.path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:im_app/data/local/drift/tables/users.dart';
|
import 'package:im_app/data/local/drift/tables/users.dart';
|
||||||
|
import 'package:im_app/data/local/drift/tables/test_tables.dart';
|
||||||
|
|
||||||
part 'app_database.g.dart';
|
part 'app_database.g.dart';
|
||||||
|
|
||||||
@DriftDatabase(tables: [Users])
|
@DriftDatabase(tables: [Users,TestTables]) //update mapping here
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
|
|
||||||
|
static Map<Type, TableInfo> getTableRegistry(GeneratedDatabase database) {
|
||||||
|
if (database is! AppDatabase) {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
User: database.users,
|
||||||
|
TestTable: database.testTables,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
AppDatabase(super.e);
|
AppDatabase(super.e);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -37,4 +50,6 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
41
apps/im_app/lib/data/local/drift/tables/test_tables.dart
Normal file
41
apps/im_app/lib/data/local/drift/tables/test_tables.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
@DataClassName('TestTable')
|
||||||
|
class TestTables extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
IntColumn get uid => integer().nullable()();
|
||||||
|
TextColumn get uuid => text().nullable()();
|
||||||
|
IntColumn get lastOnline => integer().nullable()();
|
||||||
|
TextColumn get profilePic => text().nullable()();
|
||||||
|
TextColumn get profilePicGaussian => text().withDefault(const Constant(''))();
|
||||||
|
TextColumn get nickname => text().nullable()();
|
||||||
|
TextColumn get depositName => text().nullable()();
|
||||||
|
IntColumn get hasSetDepositName => integer().withDefault(const Constant(0))();
|
||||||
|
TextColumn get contact => text().nullable()();
|
||||||
|
TextColumn get countryCode => text().nullable()();
|
||||||
|
TextColumn get username => text().nullable()();
|
||||||
|
IntColumn get role => integer().nullable()();
|
||||||
|
IntColumn get relationship => integer().nullable()();
|
||||||
|
IntColumn get friendStatus => integer().nullable()();
|
||||||
|
TextColumn get bio => text().nullable()();
|
||||||
|
TextColumn get userAlias => text().nullable()();
|
||||||
|
IntColumn get requestAt => integer().nullable()();
|
||||||
|
IntColumn get deletedAt => integer().nullable()();
|
||||||
|
TextColumn get email => text().nullable()();
|
||||||
|
TextColumn get recoveryEmail => text().nullable()();
|
||||||
|
TextColumn get remark => text().nullable()();
|
||||||
|
TextColumn get source => text().nullable()();
|
||||||
|
IntColumn get addIndex => integer().nullable()();
|
||||||
|
IntColumn get incomingSoundId => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get outgoingSoundId => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get notificationSoundId => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get sendMessageSoundId => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get groupNotificationSoundId => integer().withDefault(const Constant(0))();
|
||||||
|
TextColumn get groupTags => text().withDefault(const Constant('[]'))();
|
||||||
|
TextColumn get friendTags => text().withDefault(const Constant('[]'))();
|
||||||
|
TextColumn get publicKey => text().nullable()();
|
||||||
|
IntColumn get configBits => integer().withDefault(const Constant(0))();
|
||||||
|
TextColumn get hint => text().nullable()();
|
||||||
|
@override
|
||||||
|
String get tableName => 'test_tables';
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:im_app/app/di/db_provider.dart';
|
||||||
|
import 'package:im_app/data/local/drift/app_database.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'chat_db_test_view_model.g.dart';
|
||||||
|
|
||||||
|
class TestResult {
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final String duration;
|
||||||
|
|
||||||
|
TestResult({
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
required this.duration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatDbTestState {
|
||||||
|
final bool testStarted;
|
||||||
|
final List<TestResult> testResults;
|
||||||
|
final String currentState;
|
||||||
|
|
||||||
|
const ChatDbTestState({
|
||||||
|
this.testStarted = false,
|
||||||
|
this.testResults = const [],
|
||||||
|
this.currentState = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
ChatDbTestState copyWith({
|
||||||
|
bool? testStarted,
|
||||||
|
List<TestResult>? testResults,
|
||||||
|
String? currentState,
|
||||||
|
}) => ChatDbTestState(
|
||||||
|
testStarted: testStarted ?? this.testStarted,
|
||||||
|
testResults: testResults ?? this.testResults,
|
||||||
|
currentState: currentState ?? this.currentState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ChatDbTestViewModel extends _$ChatDbTestViewModel {
|
||||||
|
|
||||||
|
@override
|
||||||
|
ChatDbTestState build() {
|
||||||
|
// 这里就是 onInit
|
||||||
|
final List<TestResult> testResults = List.generate(
|
||||||
|
1000,
|
||||||
|
(i) => TestResult(
|
||||||
|
title: '用户 ${Random().nextInt(9999)}',
|
||||||
|
subtitle: 'uid: ${Random().nextInt(999999)}',
|
||||||
|
duration: '${Random().nextInt(500)}ms',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return ChatDbTestState(testResults: testResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 导航(Demo 按钮,正式开发后随 UI 一并替换) ──────────────────────────
|
||||||
|
|
||||||
|
/// 开始测试
|
||||||
|
void startDBTest(BuildContext context) {
|
||||||
|
state = state.copyWith(testStarted: true, currentState: '开始测试');
|
||||||
|
_testDBInsert();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 结束测试
|
||||||
|
void stopDBTest(BuildContext context) {
|
||||||
|
state = state.copyWith(testStarted: false, currentState: '结束测试');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testDBInsert() async {
|
||||||
|
final db = ref.read(storageSdkProvider);
|
||||||
|
const count = 10000;
|
||||||
|
const chunkSize = 50;
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
debugPrint('开始测试: $count 条,每批 $chunkSize 条');
|
||||||
|
|
||||||
|
int completed = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i += chunkSize) {
|
||||||
|
final chunk = List.generate(
|
||||||
|
chunkSize.clamp(0, count - i),
|
||||||
|
(j) => UsersCompanion.insert(
|
||||||
|
uid: Value(i + j),
|
||||||
|
nickname: Value('User ${i + j}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.batchInsertOrReplace<User>(chunk);
|
||||||
|
completed += chunk.length;
|
||||||
|
|
||||||
|
// 让出主线程
|
||||||
|
await Future.delayed(Duration.zero);
|
||||||
|
|
||||||
|
debugPrint('已完成: $completed / $count (${stopwatch.elapsedMilliseconds}ms)');
|
||||||
|
|
||||||
|
// 更新 UI 状态
|
||||||
|
if (ref.mounted) {
|
||||||
|
state = state.copyWith(
|
||||||
|
currentState: '已插入 $completed / $count 条',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('全部完成: ${stopwatch.elapsedMilliseconds}ms');
|
||||||
|
if (ref.mounted) {
|
||||||
|
state = state.copyWith(
|
||||||
|
testStarted: false,
|
||||||
|
currentState: '完成!共 $count 条,耗时 ${stopwatch.elapsedMilliseconds}ms',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,6 +55,11 @@ class ChatViewModel extends _$ChatViewModel {
|
|||||||
context.go(AppRouteName.settings.path);
|
context.go(AppRouteName.settings.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 测试数据库性能
|
||||||
|
void goToDatabaseTest(BuildContext context) {
|
||||||
|
context.push(AppRouteName.chatDBTest.path);
|
||||||
|
}
|
||||||
|
|
||||||
// ── 业务 ─────────────────────────────────────────────────────────────────
|
// ── 业务 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 退出登录
|
/// 退出登录
|
||||||
|
|||||||
71
apps/im_app/lib/features/chat/view/chat_db_test_page.dart
Normal file
71
apps/im_app/lib/features/chat/view/chat_db_test_page.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:im_app/features/chat/presentation/chat_db_test_view_model.dart';
|
||||||
|
|
||||||
|
import '../../../core/ui/components/app_button.dart';
|
||||||
|
import '../presentation/chat_view_model.dart';
|
||||||
|
|
||||||
|
/// 聊天页(Demo 按钮)
|
||||||
|
///
|
||||||
|
/// 包含五个演示按钮,覆盖 go_router 的常见导航场景:
|
||||||
|
/// - 「切换 Tab」 — go,替换历史,不可返回
|
||||||
|
/// - 「有参 push(extra)」 — push + extra(Dart Record),可返回
|
||||||
|
/// - 「有参 push(路径参数)」— push + URL 内嵌 id,可返回
|
||||||
|
/// - 「无参 push」 — push,可返回
|
||||||
|
/// - 「退出登录」 — 守卫自动重定向到 /login
|
||||||
|
///
|
||||||
|
/// 所有操作通过 [ChatViewModel] 处理,View 不直接调用路由。
|
||||||
|
/// 正式开发后替换为会话列表,按钮相关代码一并清除。
|
||||||
|
class ChatDbTestPage extends ConsumerWidget {
|
||||||
|
const ChatDbTestPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final vm = ref.read(chatDbTestViewModelProvider.notifier);
|
||||||
|
final state = ref.watch(chatDbTestViewModelProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('测试数据库'), ),
|
||||||
|
body: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsGeometry.symmetric(horizontal: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
AppButton.inverse(
|
||||||
|
label: state.testStarted ? '结束' : '开始',
|
||||||
|
onPressed: () => state.testStarted ? vm.stopDBTest(context) : vm.startDBTest(context),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
state.currentState,
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: state.testResults.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final result = state.testResults[index];
|
||||||
|
return ListTile(
|
||||||
|
titleAlignment: ListTileTitleAlignment.center,
|
||||||
|
title: Text(result.title),
|
||||||
|
subtitle: Text(result.subtitle),
|
||||||
|
// trailing: Text(result.duration),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,10 @@ class ChatPage extends ConsumerWidget {
|
|||||||
label: '无参 go',
|
label: '无参 go',
|
||||||
onPressed: () => vm.goToSettings(context),
|
onPressed: () => vm.goToSettings(context),
|
||||||
),
|
),
|
||||||
|
AppButton.inverse(
|
||||||
|
label: '测试数据库性能',
|
||||||
|
onPressed: () => vm.goToDatabaseTest(context),
|
||||||
|
),
|
||||||
AppButton.secondary(
|
AppButton.secondary(
|
||||||
label: '退出登录',
|
label: '退出登录',
|
||||||
fullWidth: false,
|
fullWidth: false,
|
||||||
|
|||||||
0
packages/im_log_sdk/build.yaml
Normal file
0
packages/im_log_sdk/build.yaml
Normal file
@@ -45,7 +45,7 @@ class DatabaseDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final file = File('${dbDir.path}/$uid.sqlite');
|
final file = File('${dbDir.path}/$uid.sqlite');
|
||||||
_db = _databaseFactory(NativeDatabase(file));
|
_db = _databaseFactory(NativeDatabase.createInBackground(file));
|
||||||
return _db!;
|
return _db!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import '../wiring/storage_sdk_wiring.dart';
|
|||||||
|
|
||||||
/// 本地数据库的统一公开接口。
|
/// 本地数据库的统一公开接口。
|
||||||
///
|
///
|
||||||
/// 通过 [StorageSdkApi] 工厂方法获取实例,传入 App 侧的数据库工厂即可:
|
/// 通过 [StorageSdkApi] 工厂方法获取实例,传入 App 侧的数据库工厂和表注册表即可:
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final api = StorageSdkApi(
|
/// final api = StorageSdkApi(
|
||||||
/// databaseFactory: (executor) => AppDatabase(executor),
|
/// databaseFactory: (executor) => AppDatabase(executor),
|
||||||
|
/// tableRegistry: (db) => {
|
||||||
|
/// User: (db as AppDatabase).users,
|
||||||
|
/// },
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@@ -19,98 +22,124 @@ import '../wiring/storage_sdk_wiring.dart';
|
|||||||
///
|
///
|
||||||
/// 内部接口,仅供 App 层生命周期管理使用
|
/// 内部接口,仅供 App 层生命周期管理使用
|
||||||
abstract class StorageSdkLifecycle {
|
abstract class StorageSdkLifecycle {
|
||||||
|
/// 打开指定用户的数据库(按 uid 隔离文件)。
|
||||||
Future<void> openDatabase(int uid);
|
Future<void> openDatabase(int uid);
|
||||||
|
|
||||||
|
/// 关闭当前数据库连接。
|
||||||
Future<void> closeDatabase();
|
Future<void> closeDatabase();
|
||||||
|
|
||||||
|
/// 是否已开库。
|
||||||
bool get isDatabaseOpen;
|
bool get isDatabaseOpen;
|
||||||
}
|
}
|
||||||
///
|
|
||||||
///
|
|
||||||
/// ## CRUD
|
/// ## CRUD
|
||||||
/// 所有 CRUD 方法均为泛型,接受 Drift 的 [TableInfo],与具体业务表解耦。
|
/// 所有 CRUD 方法均通过泛型数据类型查找对应表,与具体业务表解耦。
|
||||||
/// App 层传入自己定义的 Table 即可复用全部操作。
|
/// App 层通过 [tableRegistry] 注入表映射,无需在调用时传入 [TableInfo]。
|
||||||
abstract class StorageSdkApi {
|
abstract class StorageSdkApi {
|
||||||
/// 创建 SDK 实例。
|
/// 创建 SDK 实例。
|
||||||
///
|
///
|
||||||
/// [databaseFactory] 由 App 层提供:接受 [QueryExecutor],
|
/// [databaseFactory] 由 App 层提供:接受 [QueryExecutor],
|
||||||
/// 返回自定义的 [GeneratedDatabase] 子类(含表定义和迁移策略)。
|
/// 返回自定义的 [GeneratedDatabase] 子类(含表定义和迁移策略)。
|
||||||
|
///
|
||||||
|
/// [tableRegistry] 由 App 层提供:将数据类映射到对应的表信息,
|
||||||
|
/// storage_sdk 不感知具体表结构。
|
||||||
|
///
|
||||||
|
/// 示例:
|
||||||
|
/// ```dart
|
||||||
|
/// final api = StorageSdkApi(
|
||||||
|
/// databaseFactory: (executor) => AppDatabase(executor),
|
||||||
|
/// tableRegistry: (db) => {
|
||||||
|
/// User: (db as AppDatabase).users,
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
factory StorageSdkApi({
|
factory StorageSdkApi({
|
||||||
required GeneratedDatabase Function(QueryExecutor) databaseFactory,
|
required GeneratedDatabase Function(QueryExecutor) databaseFactory,
|
||||||
|
required Map<Type, TableInfo> Function(GeneratedDatabase) tableRegistry,
|
||||||
}) =>
|
}) =>
|
||||||
StorageSdkWiring.build(databaseFactory: databaseFactory);
|
StorageSdkWiring.build(
|
||||||
|
databaseFactory: databaseFactory,
|
||||||
|
tableRegistry: tableRegistry,
|
||||||
|
);
|
||||||
|
|
||||||
// ── 插入 ─────────────────────────────────────────────────────────────────
|
// ── 插入 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 插入或替换(主键冲突时覆盖)。
|
/// 插入或替换(主键冲突时覆盖)。
|
||||||
Future<void> insertOrReplace<T extends Table, D>(
|
///
|
||||||
TableInfo<T, D> table,
|
/// 示例:
|
||||||
Insertable<D> companion,
|
/// ```dart
|
||||||
);
|
/// await sdk.insertOrReplace<UsersCompanion>(
|
||||||
|
/// UsersCompanion.insert(nickname: Value('Edmund')),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
Future<void> insertOrReplace<D>(Insertable<D> companion);
|
||||||
|
|
||||||
/// 插入或忽略(主键冲突时跳过)。
|
/// 插入或忽略(主键冲突时跳过)。
|
||||||
Future<void> insert<T extends Table, D>(
|
Future<void> insert<D>(Insertable<D> companion);
|
||||||
TableInfo<T, D> table,
|
|
||||||
Insertable<D> companion,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// 批量插入或替换。
|
/// 批量插入或替换。
|
||||||
Future<void> batchInsertOrReplace<T extends Table, D>(
|
Future<void> batchInsertOrReplace<D>(List<Insertable<D>> companions);
|
||||||
TableInfo<T, D> table,
|
|
||||||
List<Insertable<D>> companions,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ── 更新 ─────────────────────────────────────────────────────────────────
|
// ── 更新 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 按条件更新。
|
/// 按条件更新。
|
||||||
Future<void> updateWhere<T extends Table, D>(
|
///
|
||||||
TableInfo<T, D> table,
|
/// 示例:
|
||||||
Insertable<D> companion,
|
/// ```dart
|
||||||
Expression<bool> Function(T) filter,
|
/// await sdk.updateWhere<User, $UsersTable>(
|
||||||
);
|
/// UsersCompanion(nickname: Value('NewName')),
|
||||||
|
/// (t) => t.uid.equals(123),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
Future<void> updateWhere<D, T extends Table>(
|
||||||
|
Insertable<D> companion,
|
||||||
|
Expression<bool> Function(T) filter,
|
||||||
|
);
|
||||||
|
|
||||||
// ── 删除 ─────────────────────────────────────────────────────────────────
|
// ── 删除 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 按条件删除。
|
/// 按条件删除。
|
||||||
Future<void> deleteWhere<T extends Table, D>(
|
Future<void> deleteWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
/// 清空整张表。
|
/// 清空整张表。
|
||||||
Future<void> deleteAll<T extends Table, D>(TableInfo<T, D> table);
|
Future<void> deleteAll<D>();
|
||||||
|
|
||||||
// ── 查询 ─────────────────────────────────────────────────────────────────
|
// ── 查询 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 查询全部记录。
|
/// 查询全部记录。
|
||||||
Future<List<D>> selectAll<T extends Table, D>(TableInfo<T, D> table);
|
///
|
||||||
|
/// 示例:
|
||||||
|
/// ```dart
|
||||||
|
/// final users = await sdk.selectAll<User>();
|
||||||
|
/// ```
|
||||||
|
Future<List<D>> selectAll<D>();
|
||||||
|
|
||||||
/// 按条件查询。
|
/// 按条件查询。
|
||||||
Future<List<D>> selectWhere<T extends Table, D>(
|
Future<List<D>> selectWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
/// 查询第一条匹配记录。
|
/// 查询第一条匹配记录。
|
||||||
Future<D?> selectFirst<T extends Table, D>(
|
Future<D?> selectFirst<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// ── 监听 ─────────────────────────────────────────────────────────────────
|
// ── 监听 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 监听全部记录(实时流)。
|
/// 监听全部记录(实时流)。
|
||||||
Stream<List<D>> watchAll<T extends Table, D>(TableInfo<T, D> table);
|
Stream<List<D>> watchAll<D>();
|
||||||
|
|
||||||
/// 按条件监听(实时流)。
|
/// 按条件监听(实时流)。
|
||||||
Stream<List<D>> watchWhere<T extends Table, D>(
|
Stream<List<D>> watchWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
/// 监听第一条匹配记录(实时流)。
|
/// 监听第一条匹配记录(实时流)。
|
||||||
Stream<D?> watchFirst<T extends Table, D>(
|
Stream<D?> watchFirst<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// ── 原始 SQL ─────────────────────────────────────────────────────────────
|
// ── 原始 SQL ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -123,8 +152,7 @@ abstract class StorageSdkApi {
|
|||||||
// ── 统计 ─────────────────────────────────────────────────────────────────
|
// ── 统计 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// 统计记录数。
|
/// 统计记录数。
|
||||||
Future<int> count<T extends Table, D>(
|
Future<int> count<D, T extends Table>({
|
||||||
TableInfo<T, D> table, {
|
|
||||||
Expression<bool> Function(T)? filter,
|
Expression<bool> Function(T)? filter,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -6,14 +6,29 @@ import 'storage_sdk_core.dart';
|
|||||||
/// [StorageSdkApi] 的实现,委托给 [StorageSdkCore]。
|
/// [StorageSdkApi] 的实现,委托给 [StorageSdkCore]。
|
||||||
class StorageSdkApiImpl implements StorageSdkApi, StorageSdkLifecycle {
|
class StorageSdkApiImpl implements StorageSdkApi, StorageSdkLifecycle {
|
||||||
final StorageSdkCore _core;
|
final StorageSdkCore _core;
|
||||||
|
final Map<Type, TableInfo> Function(GeneratedDatabase) _tableRegistry;
|
||||||
|
|
||||||
StorageSdkApiImpl({required StorageSdkCore core}) : _core = core;
|
StorageSdkApiImpl({
|
||||||
|
required StorageSdkCore core,
|
||||||
|
required Map<Type, TableInfo> Function(GeneratedDatabase) tableRegistry,
|
||||||
|
}) : _core = core,
|
||||||
|
_tableRegistry = tableRegistry;
|
||||||
|
|
||||||
|
// ── 表查找 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// 根据泛型数据类型从注册表中查找对应的 [TableInfo]。
|
||||||
|
TableInfo<T, D> _tableFor<T extends Table, D>() {
|
||||||
|
final db = _core.dataSource.current;
|
||||||
|
if (db == null) throw StateError('数据库未开启,请先调用 openDatabase()');
|
||||||
|
final table = _tableRegistry(db)[D];
|
||||||
|
if (table == null) throw StateError('未注册类型 $D 对应的表,请检查 tableRegistry');
|
||||||
|
return table as TableInfo<T, D>;
|
||||||
|
}
|
||||||
|
|
||||||
// ── 生命周期 ─────────────────────────────────────────────────────────────
|
// ── 生命周期 ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> openDatabase(int uid) =>
|
Future<void> openDatabase(int uid) => _core.dataSource.openDatabase(uid);
|
||||||
_core.dataSource.openDatabase(uid);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> closeDatabase() => _core.dataSource.closeDatabase();
|
Future<void> closeDatabase() => _core.dataSource.closeDatabase();
|
||||||
@@ -24,88 +39,73 @@ class StorageSdkApiImpl implements StorageSdkApi, StorageSdkLifecycle {
|
|||||||
// ── 插入 ─────────────────────────────────────────────────────────────────
|
// ── 插入 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> insertOrReplace<T extends Table, D>(
|
Future<void> insertOrReplace<D>(Insertable<D> companion) =>
|
||||||
TableInfo<T, D> table,
|
_core.repo.insertOrReplace(_tableFor<Table, D>(), companion);
|
||||||
Insertable<D> companion,
|
|
||||||
) =>
|
|
||||||
_core.repo.insertOrReplace(table, companion);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> insert<T extends Table, D>(
|
Future<void> insert<D>(Insertable<D> companion) =>
|
||||||
TableInfo<T, D> table,
|
_core.repo.insert(_tableFor<Table, D>(), companion);
|
||||||
Insertable<D> companion,
|
|
||||||
) =>
|
|
||||||
_core.repo.insert(table, companion);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> batchInsertOrReplace<T extends Table, D>(
|
Future<void> batchInsertOrReplace<D>(List<Insertable<D>> companions) =>
|
||||||
TableInfo<T, D> table,
|
_core.repo.batchInsertOrReplace(_tableFor<Table, D>(), companions);
|
||||||
List<Insertable<D>> companions,
|
|
||||||
) =>
|
|
||||||
_core.repo.batchInsertOrReplace(table, companions);
|
|
||||||
|
|
||||||
// ── 更新 ─────────────────────────────────────────────────────────────────
|
// ── 更新 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateWhere<T extends Table, D>(
|
Future<void> updateWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Insertable<D> companion,
|
||||||
Insertable<D> companion,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
) =>
|
||||||
) =>
|
_core.repo.updateWhere(_tableFor<T, D>(), companion, filter);
|
||||||
_core.repo.updateWhere(table, companion, filter);
|
|
||||||
|
|
||||||
// ── 删除 ─────────────────────────────────────────────────────────────────
|
// ── 删除 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteWhere<T extends Table, D>(
|
Future<void> deleteWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
) =>
|
||||||
) =>
|
_core.repo.deleteWhere(_tableFor<T, D>(), filter);
|
||||||
_core.repo.deleteWhere(table, filter);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteAll<T extends Table, D>(TableInfo<T, D> table) =>
|
Future<void> deleteAll<D>() =>
|
||||||
_core.repo.deleteAll(table);
|
_core.repo.deleteAll(_tableFor<Table, D>());
|
||||||
|
|
||||||
// ── 查询 ─────────────────────────────────────────────────────────────────
|
// ── 查询 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<D>> selectAll<T extends Table, D>(TableInfo<T, D> table) =>
|
Future<List<D>> selectAll<D>() =>
|
||||||
_core.repo.selectAll(table);
|
_core.repo.selectAll(_tableFor<Table, D>());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<D>> selectWhere<T extends Table, D>(
|
Future<List<D>> selectWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
) =>
|
||||||
) =>
|
_core.repo.selectWhere(_tableFor<T, D>(), filter);
|
||||||
_core.repo.selectWhere(table, filter);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<D?> selectFirst<T extends Table, D>(
|
Future<D?> selectFirst<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
) =>
|
||||||
) =>
|
_core.repo.selectFirst(_tableFor<T, D>(), filter);
|
||||||
_core.repo.selectFirst(table, filter);
|
|
||||||
|
|
||||||
// ── 监听 ─────────────────────────────────────────────────────────────────
|
// ── 监听 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<D>> watchAll<T extends Table, D>(TableInfo<T, D> table) =>
|
Stream<List<D>> watchAll<D>() =>
|
||||||
_core.repo.watchAll(table);
|
_core.repo.watchAll(_tableFor<Table, D>());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<D>> watchWhere<T extends Table, D>(
|
Stream<List<D>> watchWhere<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
) =>
|
||||||
) =>
|
_core.repo.watchWhere(_tableFor<T, D>(), filter);
|
||||||
_core.repo.watchWhere(table, filter);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<D?> watchFirst<T extends Table, D>(
|
Stream<D?> watchFirst<D, T extends Table>(
|
||||||
TableInfo<T, D> table,
|
Expression<bool> Function(T) filter,
|
||||||
Expression<bool> Function(T) filter,
|
) =>
|
||||||
) =>
|
_core.repo.watchFirst(_tableFor<T, D>(), filter);
|
||||||
_core.repo.watchFirst(table, filter);
|
|
||||||
|
|
||||||
// ── 原始 SQL ─────────────────────────────────────────────────────────────
|
// ── 原始 SQL ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -120,9 +120,8 @@ class StorageSdkApiImpl implements StorageSdkApi, StorageSdkLifecycle {
|
|||||||
// ── 统计 ─────────────────────────────────────────────────────────────────
|
// ── 统计 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> count<T extends Table, D>(
|
Future<int> count<D, T extends Table>({
|
||||||
TableInfo<T, D> table, {
|
|
||||||
Expression<bool> Function(T)? filter,
|
Expression<bool> Function(T)? filter,
|
||||||
}) =>
|
}) =>
|
||||||
_core.repo.count(table, filter: filter);
|
_core.repo.count(_tableFor<T, D>(), filter: filter);
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,15 @@ import 'storage_sdk_api_impl.dart';
|
|||||||
|
|
||||||
/// SDK 依赖装配入口。
|
/// SDK 依赖装配入口。
|
||||||
///
|
///
|
||||||
/// 调用方传入数据库工厂,SDK 负责连接生命周期和 CRUD 机制。
|
/// 调用方传入数据库工厂和表注册表,SDK 负责连接生命周期和 CRUD 机制。
|
||||||
///
|
///
|
||||||
/// 示例(im_app 的 DI 层):
|
/// 示例(im_app 的 DI 层):
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// StorageSdkWiring.build(
|
/// StorageSdkWiring.build(
|
||||||
/// databaseFactory: (executor) => AppDatabase(executor),
|
/// databaseFactory: (executor) => AppDatabase(executor),
|
||||||
|
/// tableRegistry: (db) => {
|
||||||
|
/// User: (db as AppDatabase).users,
|
||||||
|
/// },
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
class StorageSdkWiring {
|
class StorageSdkWiring {
|
||||||
@@ -21,10 +24,11 @@ class StorageSdkWiring {
|
|||||||
|
|
||||||
static StorageSdkApi build({
|
static StorageSdkApi build({
|
||||||
required GeneratedDatabase Function(QueryExecutor) databaseFactory,
|
required GeneratedDatabase Function(QueryExecutor) databaseFactory,
|
||||||
|
required Map<Type, TableInfo> Function(GeneratedDatabase) tableRegistry,
|
||||||
}) {
|
}) {
|
||||||
final dataSource = DatabaseDataSource(databaseFactory: databaseFactory);
|
final dataSource = DatabaseDataSource(databaseFactory: databaseFactory);
|
||||||
final repo = DatabaseRepositoryImpl(dataSource);
|
final repo = DatabaseRepositoryImpl(dataSource);
|
||||||
final core = StorageSdkCore(dataSource: dataSource, repo: repo);
|
final core = StorageSdkCore(dataSource: dataSource, repo: repo);
|
||||||
return StorageSdkApiImpl(core: core);
|
return StorageSdkApiImpl(core: core, tableRegistry: tableRegistry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ melos:
|
|||||||
|
|
||||||
gen:
|
gen:
|
||||||
description: "Run build_runner build in all packages that use it"
|
description: "Run build_runner build in all packages that use it"
|
||||||
run: melos exec --depends-on="build_runner" -- dart run build_runner build --delete-conflicting-outputs
|
run: bash scripts/table_gen.sh && melos exec --depends-on="build_runner" -- dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
gen:watch:
|
gen:watch:
|
||||||
description: "Watch mode code generation in all packages that use build_runner"
|
description: "Watch mode code generation in all packages that use build_runner"
|
||||||
|
|||||||
184
scripts/table_gen.sh
Normal file
184
scripts/table_gen.sh
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 只更新 @DriftDatabase(tables: [...]), imports 和 getTableRegistry
|
||||||
|
# 其余内容保持不变
|
||||||
|
|
||||||
|
APP_DATABASE="apps/im_app/lib/data/local/drift/app_database.dart"
|
||||||
|
|
||||||
|
# ── 扫描表类 ────────────────────────────────────────────────────────────────
|
||||||
|
TABLE_CLASSES=()
|
||||||
|
DATA_CLASSES=()
|
||||||
|
GETTERS=()
|
||||||
|
IMPORTS=()
|
||||||
|
|
||||||
|
# 项目根目录(脚本所在目录的上一层)
|
||||||
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
# app_database.dart 所在目录(用于计算相对 import 路径)
|
||||||
|
APP_DATABASE_DIR="$(dirname "$ROOT_DIR/$APP_DATABASE")"
|
||||||
|
|
||||||
|
echo "🔍 扫描所有包中的 drift 表文件..."
|
||||||
|
echo " 根目录: $ROOT_DIR"
|
||||||
|
echo " app_database 目录: $APP_DATABASE_DIR"
|
||||||
|
|
||||||
|
# 搜索所有包中的 lib/data/local/drift/tables 目录下的 dart 文件
|
||||||
|
while IFS= read -r FILE; do
|
||||||
|
echo " 📄 $FILE"
|
||||||
|
|
||||||
|
# 提取继承 Table 或 View 的类名
|
||||||
|
while IFS= read -r LINE; do
|
||||||
|
CLASS=$(echo "$LINE" | sed -E "s/.*class ([A-Za-z0-9]+) extends (Table|View).*/\1/")
|
||||||
|
|
||||||
|
if [ -z "$CLASS" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
TABLE_CLASSES+=("$CLASS")
|
||||||
|
|
||||||
|
# 提取 @DataClassName 注解
|
||||||
|
DATA_CLASS=$(grep -E "@DataClassName\('([^']+)'\)" "$FILE" | sed -E "s/.*@DataClassName\('([^']+)'\).*/\1/" | head -1)
|
||||||
|
|
||||||
|
if [ -z "$DATA_CLASS" ]; then
|
||||||
|
# 无注解时去掉末尾 's' 推断数据类名
|
||||||
|
DATA_CLASS="${CLASS%s}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DATA_CLASSES+=("$DATA_CLASS")
|
||||||
|
|
||||||
|
# getter 名 = 表类名首字母小写(跟 drift 生成一致)
|
||||||
|
GETTER=$(echo "$CLASS" | awk '{print tolower(substr($0,1,1)) substr($0,2)}')
|
||||||
|
GETTERS+=("$GETTER")
|
||||||
|
|
||||||
|
# 计算 package: import 路径(找到 pubspec.yaml 确定 package 名)
|
||||||
|
PKG_IMPORT=$(python3 -c "
|
||||||
|
import os, re
|
||||||
|
|
||||||
|
file_path = '$FILE'
|
||||||
|
search = os.path.dirname(file_path)
|
||||||
|
pkg_name = None
|
||||||
|
pkg_root = None
|
||||||
|
|
||||||
|
while search != '/':
|
||||||
|
pubspec = os.path.join(search, 'pubspec.yaml')
|
||||||
|
if os.path.exists(pubspec):
|
||||||
|
with open(pubspec) as f:
|
||||||
|
for line in f:
|
||||||
|
m = re.match(r'^name:\s*(\S+)', line)
|
||||||
|
if m:
|
||||||
|
pkg_name = m.group(1)
|
||||||
|
pkg_root = search
|
||||||
|
break
|
||||||
|
break
|
||||||
|
search = os.path.dirname(search)
|
||||||
|
|
||||||
|
if pkg_name and pkg_root:
|
||||||
|
lib_path = os.path.join(pkg_root, 'lib')
|
||||||
|
rel = os.path.relpath(file_path, lib_path)
|
||||||
|
print(f'package:{pkg_name}/{rel}')
|
||||||
|
else:
|
||||||
|
db_dir = '$APP_DATABASE_DIR'
|
||||||
|
print(os.path.relpath(file_path, db_dir))
|
||||||
|
")
|
||||||
|
IMPORTS+=("$PKG_IMPORT")
|
||||||
|
|
||||||
|
echo " ✅ 表类: $CLASS → 数据类: $DATA_CLASS → getter: $GETTER"
|
||||||
|
echo " 📦 import: $PKG_IMPORT"
|
||||||
|
|
||||||
|
done < <(grep -E "^class [A-Za-z0-9]+ extends (Table|View)" "$FILE")
|
||||||
|
|
||||||
|
done < <(find "$ROOT_DIR" \
|
||||||
|
-type f \
|
||||||
|
-name "*.dart" \
|
||||||
|
-path "*/lib/data/local/drift/tables/*" \
|
||||||
|
! -path "*/build/*" \
|
||||||
|
! -path "*/.dart_tool/*" \
|
||||||
|
! -path "*/.*")
|
||||||
|
|
||||||
|
if [ ${#TABLE_CLASSES[@]} -eq 0 ]; then
|
||||||
|
echo "❌ 未找到任何 drift 表类,请检查搜索路径。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ 共找到 ${#TABLE_CLASSES[@]} 个表类:"
|
||||||
|
for i in "${!TABLE_CLASSES[@]}"; do
|
||||||
|
echo " [${TABLE_CLASSES[$i]}] → [${DATA_CLASSES[$i]}] → getter: ${GETTERS[$i]} → ${IMPORTS[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
TABLES_LIST=$(IFS=', '; echo "${TABLE_CLASSES[*]}")
|
||||||
|
echo ""
|
||||||
|
echo "📋 @DriftDatabase(tables: [$TABLES_LIST])"
|
||||||
|
|
||||||
|
# ── 用 sed 替换 @DriftDatabase(tables: [...]) ──────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "✍️ 更新 @DriftDatabase..."
|
||||||
|
sed -i '' "s/@DriftDatabase(tables: \[.*\])/@DriftDatabase(tables: [$TABLES_LIST])/" "$ROOT_DIR/$APP_DATABASE"
|
||||||
|
echo "✅ @DriftDatabase 已更新"
|
||||||
|
|
||||||
|
# ── 生成 import 块和 registry ─────────────────────────────────────────────
|
||||||
|
IMPORT_BLOCK=""
|
||||||
|
for IMPORT_PATH in "${IMPORTS[@]}"; do
|
||||||
|
IMPORT_BLOCK+="import '$IMPORT_PATH';\n"
|
||||||
|
done
|
||||||
|
|
||||||
|
REGISTRY=""
|
||||||
|
for i in "${!TABLE_CLASSES[@]}"; do
|
||||||
|
DATA="${DATA_CLASSES[$i]}"
|
||||||
|
GETTER="${GETTERS[$i]}"
|
||||||
|
REGISTRY+=" $DATA: database.$GETTER,\n"
|
||||||
|
echo " 注册: $DATA → database.$GETTER"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✍️ 更新 imports + getTableRegistry..."
|
||||||
|
|
||||||
|
python3 - "$ROOT_DIR/$APP_DATABASE" "$REGISTRY" "$IMPORT_BLOCK" << 'PYEOF'
|
||||||
|
import sys, re
|
||||||
|
|
||||||
|
file_path = sys.argv[1]
|
||||||
|
registry = sys.argv[2].replace('\\n', '\n')
|
||||||
|
imports = sys.argv[3].replace('\\n', '\n')
|
||||||
|
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 替换 import 块(保留 drift import,替换其余 table imports)
|
||||||
|
new_content = re.sub(
|
||||||
|
r"(import 'package:drift/drift\.dart';\n)(?:import '[^']+\';\n)*",
|
||||||
|
"import 'package:drift/drift.dart';\n" + imports,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
# 替换 getTableRegistry 里的第二个 return { 块(不依赖注释)
|
||||||
|
parts = new_content.split('getTableRegistry', 1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
after = parts[1]
|
||||||
|
# 跳过第一个 return {}(is! AppDatabase 那个),替换第二个
|
||||||
|
count = [0]
|
||||||
|
def replace_second(m):
|
||||||
|
count[0] += 1
|
||||||
|
if count[0] == 2:
|
||||||
|
return m.group(1) + '\n' + registry + ' ' + m.group(2)
|
||||||
|
return m.group(0)
|
||||||
|
new_after = re.sub(
|
||||||
|
r'(return \{)(.*?)(\};)',
|
||||||
|
lambda m: replace_second(re.match(r'(return \{)(.*?)(\};)', m.group(0), re.DOTALL)),
|
||||||
|
after,
|
||||||
|
flags=re.DOTALL
|
||||||
|
)
|
||||||
|
# 用更简单直接的方式:找到所有 return { 块,替换第二个
|
||||||
|
matches = list(re.finditer(r'return \{.*?\};', after, re.DOTALL))
|
||||||
|
if len(matches) >= 2:
|
||||||
|
m = matches[1]
|
||||||
|
new_after = after[:m.start()] + 'return {\n' + registry + ' };' + after[m.end():]
|
||||||
|
new_content = parts[0] + 'getTableRegistry' + new_after
|
||||||
|
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
print("✅ imports + getTableRegistry 已更新")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 完成!下一步运行:"
|
||||||
|
echo " melos run gen"
|
||||||
Reference in New Issue
Block a user