feat(mobile,account-service): 统一发送服务 + 员工目录接口 + 好友申请五态/验证消息(B-52 施工 2/3)
- 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>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user