- 登录:工号+密码走 account-service /api/login,自动登录、被踢下线回登录页 - 消息:会话列表(未读角标/免打扰/时间)、单聊群聊、文字/语音/图片/文件消息、失败重发、历史分页 - 通讯录:新的同事/我的群聊入口、按部门分组(好友 ex 字段)、搜索 - 通话:一对一语音通话(信令走 OpenIM 自定义消息 + LiveKit,token 走 /api/rtc_token) - 界面按确认效果图 m1/m2/m3 实现,主色 #3B87F5,四态视图齐全,无英文残留
121 lines
4.6 KiB
Dart
121 lines
4.6 KiB
Dart
import 'package:dio/dio.dart';
|
||
|
||
import '../config.dart';
|
||
|
||
/// 公司账号登录接口返回的数据
|
||
class AuthResult {
|
||
final String userID;
|
||
final String token;
|
||
final String nickname;
|
||
|
||
AuthResult({required this.userID, required this.token, required this.nickname});
|
||
|
||
factory AuthResult.fromJson(Map<String, dynamic> json) => AuthResult(
|
||
userID: json['userID']?.toString() ?? '',
|
||
token: json['imToken']?.toString() ?? '',
|
||
nickname: json['nickname']?.toString() ?? '',
|
||
);
|
||
}
|
||
|
||
/// 登录失败,message 直接给用户看(简体中文)
|
||
class AuthException implements Exception {
|
||
final String message;
|
||
AuthException(this.message);
|
||
|
||
@override
|
||
String toString() => message;
|
||
}
|
||
|
||
/// 公司账号登录 HTTP 客户端。
|
||
///
|
||
/// 接口契约(account-service,见仓库 account-service/README.md):
|
||
/// - POST {authApiBase}/api/login
|
||
/// body: {"staffNo": "工号", "password": "...", "platformID": 1 iOS / 2 Android}
|
||
/// 响应统一包络 {"code": 0, "msg": "", "data": {...}},失败也是 HTTP 200 + code != 0
|
||
/// 成功 data: {"userID", "nickname", "imToken", "expireTimeSeconds"}(userID/imToken 直接用于 OpenIM SDK 登录)
|
||
/// - POST {authApiBase}/api/rtc_token(语音通话换 LiveKit 进房 token)
|
||
/// 请求头: Authorization: Bearer {imToken}
|
||
/// body: {"room": "房间号", "identity": "当前用户 userID"}
|
||
/// 成功 data: {"token": "LiveKit 访问 token"}
|
||
class AuthApi {
|
||
final Dio _dio = Dio(BaseOptions(
|
||
baseUrl: authApiBase,
|
||
connectTimeout: const Duration(seconds: 10),
|
||
receiveTimeout: const Duration(seconds: 10),
|
||
));
|
||
|
||
/// 拆统一响应包络:code == 0 返回 data,否则用 msg 抛 [AuthException]
|
||
Map<String, dynamic> _unwrap(dynamic body) {
|
||
if (body is Map<String, dynamic>) {
|
||
if (body['code'] == 0 && body['data'] is Map<String, dynamic>) {
|
||
return Map<String, dynamic>.from(body['data'] as Map);
|
||
}
|
||
final msg = body['msg']?.toString();
|
||
throw AuthException((msg != null && msg.isNotEmpty) ? msg : '服务器返回的数据不对,请联系管理员');
|
||
}
|
||
throw AuthException('服务器返回的数据不对,请联系管理员');
|
||
}
|
||
|
||
/// 工号 + 密码登录,成功后返回 OpenIM 登录所需的 userID/imToken
|
||
Future<AuthResult> login({required String staffNo, required String password, required int platformID}) async {
|
||
try {
|
||
final resp = await _dio.post('/api/login', data: {
|
||
'staffNo': staffNo,
|
||
'password': password,
|
||
'platformID': platformID,
|
||
});
|
||
final result = AuthResult.fromJson(_unwrap(resp.data));
|
||
if (result.userID.isNotEmpty && result.token.isNotEmpty) return result;
|
||
throw AuthException('服务器返回的数据不对,请联系管理员');
|
||
} on DioException catch (e) {
|
||
// 401 等 HTTP 层失败也带 {code, msg} 包络
|
||
final data = e.response?.data;
|
||
if (data is Map && data['msg'] != null && data['msg'].toString().isNotEmpty) {
|
||
throw AuthException(data['msg'].toString());
|
||
}
|
||
throw AuthException('连不上服务器,请检查网络');
|
||
} catch (e) {
|
||
if (e is AuthException) rethrow;
|
||
throw AuthException('登录出错了,请稍后再试');
|
||
}
|
||
}
|
||
|
||
/// 获取 LiveKit 房间 token(语音通话用)。authToken 即登录返回的 imToken。
|
||
Future<LiveKitCredential> getRtcToken({required String room, required String identity, required String authToken}) async {
|
||
try {
|
||
final resp = await _dio.post(
|
||
'/api/rtc_token',
|
||
data: {'room': room, 'identity': identity},
|
||
options: Options(headers: {'Authorization': 'Bearer $authToken'}),
|
||
);
|
||
final data = _unwrap(resp.data);
|
||
final token = data['token']?.toString();
|
||
if (token != null && token.isNotEmpty) {
|
||
final live = data['liveURL']?.toString();
|
||
return LiveKitCredential(
|
||
token: token,
|
||
liveURL: live != null && live.isNotEmpty ? live : livekitUrl,
|
||
);
|
||
}
|
||
throw AuthException('服务器返回的数据不对,请联系管理员');
|
||
} on DioException catch (e) {
|
||
final data = e.response?.data;
|
||
if (data is Map && data['msg'] != null && data['msg'].toString().isNotEmpty) {
|
||
throw AuthException(data['msg'].toString());
|
||
}
|
||
throw AuthException('连不上服务器,请检查网络');
|
||
} catch (e) {
|
||
if (e is AuthException) rethrow;
|
||
throw AuthException('发起通话失败,请稍后再试');
|
||
}
|
||
}
|
||
}
|
||
|
||
/// LiveKit 进房凭证
|
||
class LiveKitCredential {
|
||
final String token;
|
||
final String liveURL;
|
||
|
||
LiveKitCredential({required this.token, required this.liveURL});
|
||
}
|