- message_send_service:所有消息走统一门禁(登录/连接/空接收方/好友校验), 失败打印 SDK 原始 code 并映射中文提示,不再只吞异常 - chat_screen 接入统一发送服务,失败提示真实原因 - account-service 新增 GET /api/directory(登录用户可读启用员工,用于添加同事) - add_friend_screen 增加可编辑验证消息(视觉规范 4.2) - new_friends_screen 五态 + 窄屏(<360)按钮组换行不挤压姓名(视觉规范 5.3) Co-authored-by: multica-agent <github@multica.ai>
104 lines
3.8 KiB
Dart
104 lines
3.8 KiB
Dart
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
|
|
|
import 'im_service.dart';
|
|
|
|
/// 统一消息发送服务:所有消息(文字/表情/语音/图片/文件/通话信令)都走这里。
|
|
///
|
|
/// 职责:
|
|
/// 1. 发送前置校验:已登录、连接正常、接收方非空;
|
|
/// 2. 单聊时校验好友状态(非好友明确提示,不静默失败);
|
|
/// 3. 失败时把 SDK 原始 code/message 记进日志(不含 token 与正文),
|
|
/// 并返回可直接展示的中文错误,杜绝「只吞异常看不到原因」。
|
|
class MessageSendService {
|
|
MessageSendService._();
|
|
|
|
static final MessageSendService instance = MessageSendService._();
|
|
|
|
/// 发送消息。成功返回 SDK 回执消息;失败抛出带中文提示的异常。
|
|
/// 单聊且非好友时抛 [FriendRequiredException]。
|
|
Future<Message> send({
|
|
required Message message,
|
|
String? userID,
|
|
String? groupID,
|
|
String? offlinePushInfo = '',
|
|
}) async {
|
|
final im = IMService.instance;
|
|
|
|
if (!im.loggedIn || im.currentUserID == null) {
|
|
throw const SendException('登录状态失效,请重新登录');
|
|
}
|
|
if (im.connectStatus != ConnectStatus.success) {
|
|
throw const SendException('正在重连,请稍后再试');
|
|
}
|
|
|
|
final isGroup = groupID != null && groupID.isNotEmpty;
|
|
final peer = userID ?? '';
|
|
if (!isGroup && peer.isEmpty) {
|
|
throw const SendException('请先添加对方为同事,再发起会话');
|
|
}
|
|
|
|
// 单聊:非好友提示先添加(OpenIM 服务端是否拦截交给服务端,但提示要明确)
|
|
if (!isGroup) {
|
|
final isFriend = await _isFriend(peer);
|
|
if (!isFriend) {
|
|
throw FriendRequiredException('请先添加对方为同事,再发送消息');
|
|
}
|
|
}
|
|
|
|
try {
|
|
final sent = await OpenIM.iMManager.messageManager.sendMessage(
|
|
message: message,
|
|
userID: isGroup ? null : peer,
|
|
groupID: isGroup ? groupID : null,
|
|
offlinePushInfo: OfflinePushInfo(),
|
|
);
|
|
return sent;
|
|
} catch (e) {
|
|
// 记录原始错误码(不含 token / 消息正文)
|
|
// ignore: avoid_print
|
|
print('[MessageSendService] 发送失败 peer=$peer groupID=$groupID err=$e');
|
|
throw SendException(_mapError(e));
|
|
}
|
|
}
|
|
|
|
/// 判断 peer 是否已是好友。查询失败按非好友处理(宁可让用户先加好友)。
|
|
Future<bool> _isFriend(String userID) async {
|
|
try {
|
|
final friends = await OpenIM.iMManager.friendshipManager.getFriendList();
|
|
return friends.any((f) => (f.userID ?? f.friendUserID) == userID);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 把 SDK 原始错误映射成中文提示
|
|
String _mapError(dynamic e) {
|
|
final s = e.toString();
|
|
// SDK 错误码常量,见 flutter_openim_sdk/lib/src/enum/sdk_error_code.dart
|
|
if (s.contains('1303')) return '对方还不是你的同事,请先添加对方';
|
|
if (s.contains('1304')) return '已经是同事了,可以正常聊天';
|
|
if (s.contains('1302')) return '对方已把你加入黑名单,无法发送';
|
|
if (s.contains('1101')) return '对方账号不存在';
|
|
if (s.contains('1501')) return '登录已过期,请重新登录';
|
|
if (s.contains('1502') || s.contains('1503')) return '登录凭证无效,请重新登录';
|
|
if (s.contains('10000') || s.contains('10001') || s.contains('网络') || s.contains('timeout')) {
|
|
return '网络异常,请检查网络后重试';
|
|
}
|
|
return '消息没发出去,请重试';
|
|
}
|
|
}
|
|
|
|
/// 发送失败的中文提示
|
|
class SendException implements Exception {
|
|
final String message;
|
|
const SendException(this.message);
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|
|
|
|
/// 单聊非好友时的明确提示
|
|
class FriendRequiredException extends SendException {
|
|
const FriendRequiredException(super.message);
|
|
}
|