更新测试案例

This commit is contained in:
Happi (哈比)
2026-03-09 13:03:44 +08:00
parent 56112e1fe3
commit 7b78da86e7
11 changed files with 721 additions and 278 deletions

View File

@@ -1,148 +0,0 @@
import 'package:drift/drift.dart';
import 'package:im_app/data/local/drift/app_database.dart';
import 'package:im_app/domain/entities/user.dart';
/// 用户 DTOData Transfer Object
///
/// local / remote 共用的数据传输对象,放在 data/models/。
/// 提供与 Domain Entity [User] 之间的双向转换。
class UserDto {
final int uid;
final String? uuid;
final int? lastOnline;
final String? profilePic;
final String? profilePicGaussian;
final String? nickname;
final String? contact;
final String? countryCode;
final String? email;
final String? recoveryEmail;
final String? username;
final String? bio;
final int? relationship;
final String? userAlias;
final int? channelId;
final int? channelGroupId;
final String? hint;
const UserDto({
required this.uid,
this.uuid,
this.lastOnline,
this.profilePic,
this.profilePicGaussian,
this.nickname,
this.contact,
this.countryCode,
this.email,
this.recoveryEmail,
this.username,
this.bio,
this.relationship,
this.userAlias,
this.channelId,
this.channelGroupId,
this.hint,
});
factory UserDto.fromJson(Map<String, dynamic> json) => UserDto(
uid: json['uid'] as int,
uuid: json['uuid'],
lastOnline: json['last_online'],
profilePic: json['profile_pic'],
profilePicGaussian: json['profile_pic_gaussian'] ?? '',
nickname: json['nickname'],
contact: json['contact'],
countryCode: json['country_code'],
email: json['email'],
recoveryEmail: json['recovery_email'] ?? '',
username: json['username'],
bio: json['bio'] ?? '',
relationship: json['relationship'],
userAlias: json['user_alias'],
channelId: json['channel_id'],
channelGroupId: json['channel_group_id'],
hint: json['hint'],
);
Map<String, dynamic> toJson() => {
'uid': uid,
'uuid': uuid,
'last_online': lastOnline,
'profile_pic': profilePic,
'profile_pic_gaussian': profilePicGaussian,
'nickname': nickname,
'contact': contact,
'country_code': countryCode,
'email': email,
'recovery_email': recoveryEmail,
'username': username,
'bio': bio,
'relationship': relationship,
'user_alias': userAlias,
'channel_id': channelId,
'channel_group_id': channelGroupId,
'hint': hint,
};
/// DTO → Domain Entity
User toEntity() => User(
uid: uid,
uuid: uuid,
lastOnline: lastOnline,
profilePic: profilePic,
profilePicGaussian: profilePicGaussian,
nickname: nickname,
contact: contact,
countryCode: countryCode,
email: email,
recoveryEmail: recoveryEmail,
username: username,
bio: bio,
relationship: relationship,
userAlias: userAlias,
channelId: channelId,
channelGroupId: channelGroupId,
hint: hint,
);
/// Domain Entity → DTO
factory UserDto.fromEntity(User user) => UserDto(
uid: user.uid,
uuid: user.uuid,
lastOnline: user.lastOnline,
profilePic: user.profilePic,
profilePicGaussian: user.profilePicGaussian,
nickname: user.nickname,
contact: user.contact,
countryCode: user.countryCode,
email: user.email,
recoveryEmail: user.recoveryEmail,
username: user.username,
bio: user.bio,
relationship: user.relationship,
userAlias: user.userAlias,
channelId: user.channelId,
channelGroupId: user.channelGroupId,
hint: user.hint,
);
/// DTO → Drift Companion (for DB insert/update)
UsersCompanion toCompanion() => UsersCompanion(
uid: Value(uid),
uuid: Value(uuid),
lastOnline: Value(lastOnline),
profilePic: Value(profilePic),
profilePicGaussian: Value(profilePicGaussian ?? ''),
nickname: Value(nickname),
contact: Value(contact),
countryCode: Value(countryCode),
email: Value(email),
recoveryEmail: Value(recoveryEmail),
username: Value(username),
bio: Value(bio),
relationship: Value(relationship),
userAlias: Value(userAlias),
hint: Value(hint),
);
}

View File

@@ -93,8 +93,6 @@ class ProfileData {
bio: bio,
relationship: relationship,
userAlias: userAlias,
channelId: channelId,
channelGroupId: channelGroupId,
hint: hint,
);
}

View File

@@ -88,8 +88,6 @@ class LoginProfile {
bio: bio,
relationship: relationship,
userAlias: userAlias,
channelId: channelId,
channelGroupId: channelGroupId,
hint: hint,
);
}
@@ -167,4 +165,4 @@ class LoginRequest extends ApiRequestable<LoginResponse>
@override
Map<String, dynamic> toJson() => _$LoginRequestToJson(this);
}
}

View File

@@ -0,0 +1,238 @@
import 'package:drift/drift.dart';
import 'package:im_app/data/local/drift/app_database.dart';
import 'package:im_app/domain/entities/user.dart';
import 'package:im_app/domain/repositories/user_repository.dart';
import 'package:storage_sdk/storage_sdk.dart';
/// 用户仓储实现
///
/// ## 职责
/// - 所有 DB 操作通过 [StorageSdkApi],不直接接触 AppDatabase
/// - DriftUser ↔ Domain User 映射
/// - 持久化决策由调用方决定
///
/// ## 数据流
/// ```
/// 网络User.fromJson(json) → (可选) saveUser(user) → StorageSdkApi → DB
/// 监听StorageSdkApi.watchWhere/watchAll → DriftUser → _toEntity() → UI
/// 部分更新updateFields(uid, UsersCompanion) → StorageSdkApi.updateWhere
/// ```
class UserRepositoryImpl implements UserRepository {
final StorageSdkApi _storage;
UserRepositoryImpl(this._storage);
// ── DB row → Domain ──────────────────────────────────────────────────────
User _toEntity(DriftUser row) => User(
uid: row.uid,
uuid: row.uuid,
lastOnline: row.lastOnline,
profilePic: row.profilePic,
profilePicGaussian: row.profilePicGaussian,
nickname: row.nickname,
depositName: row.depositName,
hasSetDepositName: row.hasSetDepositName,
contact: row.contact,
countryCode: row.countryCode,
username: row.username,
role: row.role,
relationship: row.relationship,
friendStatus: row.friendStatus,
bio: row.bio,
userAlias: row.userAlias,
requestAt: row.requestAt,
deletedAt: row.deletedAt,
email: row.email,
recoveryEmail: row.recoveryEmail,
remark: row.remark,
source: row.source,
addIndex: row.addIndex,
incomingSoundId: row.incomingSoundId,
outgoingSoundId: row.outgoingSoundId,
notificationSoundId: row.notificationSoundId,
sendMessageSoundId: row.sendMessageSoundId,
groupNotificationSoundId: row.groupNotificationSoundId,
groupTags: row.groupTags,
friendTags: row.friendTags,
publicKey: row.publicKey,
configBits: row.configBits,
hint: row.hint,
);
// ── Domain → DB companion ────────────────────────────────────────────────
UsersCompanion _toCompanion(User user) => UsersCompanion(
uid: Value(user.uid),
uuid: Value(user.uuid),
lastOnline: Value(user.lastOnline),
profilePic: Value(user.profilePic),
profilePicGaussian: Value(user.profilePicGaussian ?? ''),
nickname: Value(user.nickname),
depositName: Value(user.depositName),
hasSetDepositName: Value(user.hasSetDepositName ?? 0),
contact: Value(user.contact),
countryCode: Value(user.countryCode),
username: Value(user.username),
role: Value(user.role),
relationship: Value(user.relationship),
friendStatus: Value(user.friendStatus),
bio: Value(user.bio),
userAlias: Value(user.userAlias),
requestAt: Value(user.requestAt),
deletedAt: Value(user.deletedAt),
email: Value(user.email),
recoveryEmail: Value(user.recoveryEmail),
remark: Value(user.remark),
source: Value(user.source),
addIndex: Value(user.addIndex),
incomingSoundId: Value(user.incomingSoundId ?? 0),
outgoingSoundId: Value(user.outgoingSoundId ?? 0),
notificationSoundId: Value(user.notificationSoundId ?? 0),
sendMessageSoundId: Value(user.sendMessageSoundId ?? 0),
groupNotificationSoundId: Value(user.groupNotificationSoundId ?? 0),
groupTags: Value(user.groupTags ?? '[]'),
friendTags: Value(user.friendTags ?? '[]'),
publicKey: Value(user.publicKey),
configBits: Value(user.configBits ?? 0),
hint: Value(user.hint),
);
// ── 监听 ─────────────────────────────────────────────────────────────────
/// 监听单个用户
@override
Stream<User?> watchUser(int uid) {
return _storage
.watchFirst<DriftUser, $UsersTable>((t) => t.uid.equals(uid))
.map((row) => row != null ? _toEntity(row) : null);
}
/// 监听指定 uid 列表
@override
Stream<List<User>> watchUsers(List<int> uids) {
return _storage
.watchWhere<DriftUser, $UsersTable>((t) => t.uid.isIn(uids))
.map((rows) => rows.map(_toEntity).toList());
}
/// 监听所有用户
@override
Stream<List<User>> watchAllUsers() {
return _storage.watchAll<DriftUser>().map(
(rows) => rows.map(_toEntity).toList(),
);
}
// ── 读取 ─────────────────────────────────────────────────────────────────
@override
Future<User?> getUser(int uid) async {
final row = await _storage.selectFirst<DriftUser, $UsersTable>(
(t) => t.uid.equals(uid),
);
return row != null ? _toEntity(row) : null;
}
@override
Future<List<User>> getAllUsers() async {
final rows = await _storage.selectAll<DriftUser>();
return rows.map(_toEntity).toList();
}
@override
Future<List<User>> getUsers({required int offset, required int limit}) async {
final rows = await _storage.rawQuery(
'SELECT * FROM user ORDER BY id LIMIT ? OFFSET ?',
[limit, offset],
);
return rows
.map(
(row) => User(
uid: row.read<int>('uid'),
uuid: row.readNullable<String>('uuid'),
lastOnline: row.readNullable<int>('last_online'),
profilePic: row.readNullable<String>('profile_pic'),
profilePicGaussian: row.readNullable<String>(
'profile_pic_gaussian',
),
nickname: row.readNullable<String>('nickname'),
depositName: row.readNullable<String>('deposit_name'),
hasSetDepositName: row.readNullable<int>('has_set_deposit_name'),
contact: row.readNullable<String>('contact'),
countryCode: row.readNullable<String>('country_code'),
username: row.readNullable<String>('username'),
role: row.readNullable<int>('role'),
relationship: row.readNullable<int>('relationship'),
friendStatus: row.readNullable<int>('friend_status'),
bio: row.readNullable<String>('bio'),
userAlias: row.readNullable<String>('user_alias'),
requestAt: row.readNullable<int>('request_at'),
deletedAt: row.readNullable<int>('deleted_at'),
email: row.readNullable<String>('email'),
recoveryEmail: row.readNullable<String>('recovery_email'),
remark: row.readNullable<String>('remark'),
source: row.readNullable<String>('source'),
addIndex: row.readNullable<int>('add_index'),
incomingSoundId: row.readNullable<int>('incoming_sound_id'),
outgoingSoundId: row.readNullable<int>('outgoing_sound_id'),
notificationSoundId: row.readNullable<int>('notification_sound_id'),
sendMessageSoundId: row.readNullable<int>('send_message_sound_id'),
groupNotificationSoundId: row.readNullable<int>(
'group_notification_sound_id',
),
groupTags: row.readNullable<String>('group_tags'),
friendTags: row.readNullable<String>('friend_tags'),
publicKey: row.readNullable<String>('public_key'),
configBits: row.readNullable<int>('config_bits'),
hint: row.readNullable<String>('hint'),
),
)
.toList();
}
@override
Future<int> countUsers() async {
return _storage.count<DriftUser, $UsersTable>();
}
// ── 写入 ─────────────────────────────────────────────────────────────────
@override
Future<void> saveUser(User user) async {
await _storage.insertOrReplace<DriftUser>(_toCompanion(user));
}
@override
Future<void> saveUsers(List<User> users) async {
await _storage.batchInsertOrReplace<DriftUser>(
users.map(_toCompanion).toList(),
);
}
/// 仅更新指定列,其他列不变
///
/// 示例:
/// ```dart
/// await userRepo.updateFields(uid, UsersCompanion(
/// nickname: Value('New Name'),
/// lastOnline: Value(DateTime.now().millisecondsSinceEpoch),
/// ));
/// ```
@override
Future<void> updateFields(int uid, UsersCompanion companion) async {
await _storage.updateWhere<DriftUser, $UsersTable>(
companion,
(t) => t.uid.equals(uid),
);
}
// ── 删除 ─────────────────────────────────────────────────────────────────
@override
Future<void> deleteUser(int uid) async {
await _storage.deleteWhere<DriftUser, $UsersTable>(
(t) => t.uid.equals(uid),
);
}
}