业务更新User所需(企业成员、聊天室群组成员)

This commit is contained in:
Happi (哈比)
2026-03-09 19:15:51 +08:00
parent c278d1329e
commit 8f77a14818
22 changed files with 1030 additions and 238 deletions

View File

@@ -0,0 +1,24 @@
import 'package:im_app/domain/entities/user.dart';
/// 企业成员的部分 [User] 表示,数据来源于企业成员接口。
///
/// 企业 API 返回的用户信息极为精简,仅包含身份、显示名称和在线状态。
/// 如需完整用户信息,请通过用户详情接口获取完整的 [User]。
///
/// 注意企业 API 的字段名与标准用户接口不同:
/// - `user_id` → [uid] (非 `uid`
/// - `name` → [nickname] (非 `nickname`
class CompanyMember extends User {
const CompanyMember({
required super.uid,
super.nickname,
super.lastOnline,
super.requireUpsert = true,
});
factory CompanyMember.fromJson(Map<String, dynamic> json) => CompanyMember(
uid: json['user_id'] as int,
nickname: json['name'],
lastOnline: json['last_online'],
);
}

View File

@@ -0,0 +1,66 @@
import 'package:im_app/domain/entities/user.dart';
/// 群组成员的部分 [User] 表示,数据来源于群组成员接口。
///
/// 群组 API 返回的用户信息不完整,仅保证身份和在线状态字段有效。
/// 构造函数中未列出的 [User] 字段均为 null不应依赖这些字段。
///
/// ## 何时使用 [GroupMember] vs [User]
/// - 渲染群组成员列表、成员头像等场景使用 [GroupMember]
/// - 需要联系方式、音效设置、好友关系等完整信息时,
/// 请通过用户详情接口获取完整的 [User]
///
/// ## 数据流
/// ```
/// 群组 API 响应
/// → GroupMember.fromJson() ← 群组专用字段user_id、icon…
/// → ★ GroupMember ★
/// → 仍是合法的 [User],可在任何需要 [User] 的地方使用
/// ```
class GroupMember extends User {
const GroupMember({
required super.uid,
super.nickname,
super.profilePic,
super.profilePicGaussian,
super.lastOnline,
super.deletedAt,
super.role,
super.requireUpsert = true,
});
/// 从群组成员 JSON 数据创建 [GroupMember]。
///
/// 注意群组 API 使用了与用户接口不同的字段名:
/// - `user_id` → [uid] (非 `uid`
/// - `user_name` → [nickname] (非 `nickname`
/// - `icon` → [profilePic] (非 `profile_pic`
/// - `icon_gaussian` → [profilePicGaussian]
/// - `delete_time` → [deletedAt] (非 `deleted_at`
factory GroupMember.fromJson(Map<String, dynamic> json) => GroupMember(
uid: json['user_id'] as int,
nickname: json['user_name'],
profilePic: json['icon'],
profilePicGaussian: json['icon_gaussian'],
lastOnline: json['last_online'],
deletedAt: json['delete_time'],
role: json['role'],
);
/// 序列化为群组成员 JSON 格式。
///
/// 字段名与 [fromJson] 保持对称。
/// `id`/`user_id`、`nickname`/`user_name`、`icon`/`profile_pic`
/// 同时输出两种命名,兼容不同约定的下游消费方。
Map<String, dynamic> toJson() => {
'id': uid,
'user_id': uid,
'user_name': nickname,
'nickname': nickname, // 兼容旧命名
'icon': profilePic,
'profile_pic': profilePic, // 兼容旧命名
'icon_gaussian': profilePicGaussian,
'last_online': lastOnline,
'delete_time': deletedAt,
};
}

View File

@@ -47,6 +47,24 @@ class User {
final int? configBits;
final String? hint;
/// 标记此用户数据是否为部分数据,需要 upsert 而非全字段覆盖。
///
/// 由响应解析层设置Repository 据此决定写入策略:
/// - true → [UserRepository.upsertUser],仅更新非 null 字段,保留本地已有数据
/// - false → [UserRepository.insertOrReplaceUser],全字段覆盖(默认)
///
/// 注意:此字段仅用于内存传递,不会被持久化到 DB。
final bool requireUpsert;
/// TODO(contacts): 添加 localName / localPhoneNumbers关联本地通讯录。
/// 这两个字段是设备侧数据,不应持久化到服务端 payload。
///
/// TODO(history): status 和 created_at 仅出现在历史用户记录中(如审计日志)。
/// 建议用独立的 UserHistory 实体承载,避免污染此 Domain 模型。
///
/// TODO(online): objectMgr.onlineMgr.updateOnlineTime() 原先在 fromJson() 内
/// 作为副作用调用。不要在此处复现——副作用应在 Repository 或 UseCase 层处理。
const User({
required this.uid,
this.uuid,
@@ -81,9 +99,13 @@ class User {
this.publicKey,
this.configBits,
this.hint,
this.requireUpsert = false,
});
/// 直接从网络 JSON 创建 Domain 实体
/// 直接从网络 JSON 创建 Domain 实体
///
/// [requireUpsert] 默认 false如响应解析层判断为部分数据
/// 可在调用后通过 copyWith(requireUpsert: true) 标记。
factory User.fromJson(Map<String, dynamic> json) => User(
uid: json['uid'] as int,
uuid: json['uuid'],
@@ -120,7 +142,10 @@ class User {
hint: json['hint'],
);
/// 仅更新部分字段
/// 仅更新部分字段,其余保持不变。
///
/// 注意:[requireUpsert] 不会随其他字段自动继承,
/// 需要显式传入以避免意外的写入策略变更。
User copyWith({
int? uid,
String? uuid,
@@ -155,6 +180,7 @@ class User {
String? publicKey,
int? configBits,
String? hint,
bool? requireUpsert,
}) {
return User(
uid: uid ?? this.uid,
@@ -191,6 +217,7 @@ class User {
publicKey: publicKey ?? this.publicKey,
configBits: configBits ?? this.configBits,
hint: hint ?? this.hint,
requireUpsert: requireUpsert ?? this.requireUpsert,
);
}
}