import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:sales_chat/models/user.dart'; import 'package:sales_chat/models/group.dart'; import 'package:sales_chat/models/message.dart'; import 'package:sales_chat/models/invite.dart'; import 'package:sales_chat/models/stats.dart'; /// 与 Tailchat 后端通信的 API 服务。 /// /// 关键约定: /// - 所有 API 路由都在 `/api/`(网关路径)下。 /// - Tailchat 核心用户路由:`/api/user/` /// - 插件路由:`/api/plugin:com.msgbyte.saleschat/`(冒号用于分隔 Moleculer 服务名段) /// - 认证使用 `X-Token` 请求头(非 Authorization: Bearer)。 /// - 网关响应格式为 `{ "code": , "data": }`。 class ApiService { late final Dio _dio; final FlutterSecureStorage _storage; static const String _baseUrl = 'http://localhost:3000/api'; static const String _tokenKey = 'auth_token'; static const String _userKey = 'user_data'; ApiService() : _storage = const FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: true), iOptions: IOSOptions( accessibility: KeychainAccessibility.first_unlock_this_device, ), ) { _dio = Dio(BaseOptions( baseUrl: _baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 30), headers: { 'Content-Type': 'application/json', }, )); _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) async { // Tailchat 使用 X-Token 请求头进行认证 final token = await _storage.read(key: _tokenKey); if (token != null) { options.headers['X-Token'] = token; } return handler.next(options); }, onError: (error, handler) async { if (error.response?.statusCode == 401) { await _storage.delete(key: _tokenKey); await _storage.delete(key: _userKey); } return handler.next(error); }, )); } Future get token => _storage.read(key: _tokenKey); Future get isAuthenticated async => await _storage.read(key: _tokenKey) != null; /// 解包网关响应:{ "code": 200, "data": ... } dynamic _unwrapResponse(Response response) { final body = response.data; if (body is Map && body.containsKey('data')) { return body['data']; } return body; } // ============================================================ // 认证 API - Tailchat 核心用户服务 // ============================================================ /// 使用邮箱或用户名 + 密码登录。 /// 后端接口:POST /api/user/login {email|username, password} /// 响应(已解包):{ ...userFields, token: "jwt..." } Future login({ String? email, String? username, required String password, }) async { final data = { 'password': password, }; if (email != null && email.isNotEmpty) { data['email'] = email; } else if (username != null && username.isNotEmpty) { data['username'] = username; } final response = await _dio.post('/user/login', data: data); final unwrapped = _unwrapResponse(response); // 登录响应是用户对象附加了 `token` final authResponse = AuthResponse.fromJson({ 'data': unwrapped, }); await _storage.write(key: _tokenKey, value: authResponse.token); await _storage.write( key: _userKey, value: jsonEncode(authResponse.user.toJson())); return authResponse; } /// 注册新用户。 /// 后端接口:POST /api/user/register {username, email, nickname, password} /// 响应(已解包):{ ...userFields, token: "jwt..." } Future register({ String? username, String? email, String? nickname, required String password, }) async { final data = { 'password': password, }; if (username != null && username.isNotEmpty) data['username'] = username; if (email != null && email.isNotEmpty) data['email'] = email; if (nickname != null && nickname.isNotEmpty) data['nickname'] = nickname; final response = await _dio.post('/user/register', data: data); final unwrapped = _unwrapResponse(response); final authResponse = AuthResponse.fromJson({ 'data': unwrapped, }); await _storage.write(key: _tokenKey, value: authResponse.token); await _storage.write( key: _userKey, value: jsonEncode(authResponse.user.toJson())); return authResponse; } /// 创建临时(访客)用户。 /// 后端接口:POST /api/user/createTemporaryUser {nickname} Future createTemporaryUser({required String nickname}) async { final response = await _dio.post('/user/createTemporaryUser', data: { 'nickname': nickname, }); final unwrapped = _unwrapResponse(response); final authResponse = AuthResponse.fromJson({ 'data': unwrapped, }); await _storage.write(key: _tokenKey, value: authResponse.token); await _storage.write( key: _userKey, value: jsonEncode(authResponse.user.toJson())); return authResponse; } /// 登出(仅本地 - JWT 是无状态的) Future logout() async { await _storage.delete(key: _tokenKey); await _storage.delete(key: _userKey); } /// 从本地缓存获取当前用户 Future getCurrentUser() async { final userData = await _storage.read(key: _userKey); if (userData == null) return null; try { return User.fromJson(jsonDecode(userData)); } catch (e) { return null; } } // ============================================================ // 群组 API - Tailchat 核心群组服务 // ============================================================ /// 获取当前用户加入的群组。 /// 后端接口:GET /api/group/getUserGroups Future> getGroups() async { final response = await _dio.get('/group/getUserGroups'); final data = _unwrapResponse(response); return (data as List).map((e) => Group.fromJson(e)).toList(); } /// 获取单个群组的基本信息。 /// 后端接口:GET /api/group/getGroupBasicInfo {groupId} Future getGroupBasicInfo(String groupId) async { final response = await _dio.get('/group/getGroupBasicInfo', queryParameters: { 'groupId': groupId, }); final data = _unwrapResponse(response); return GroupBasicInfo.fromJson(data); } /// 获取完整群组信息。 /// 后端接口:GET /api/group/getGroupInfo {groupId} Future getGroupInfo(String groupId) async { final response = await _dio.get('/group/getGroupInfo', queryParameters: { 'groupId': groupId, }); final data = _unwrapResponse(response); return Group.fromJson(data); } /// 创建新群组。 /// 后端接口:POST /api/group/createGroup {name, panels} Future createGroup(String name, {String? description}) async { // Tailchat 创建群组时至少需要一个文本面板 final response = await _dio.post('/group/createGroup', data: { 'name': name, 'panels': [ { 'id': 'default', 'name': 'General', 'type': 0, // GroupPanelType.TEXT(文本面板类型) } ], }); final data = _unwrapResponse(response); return Group.fromJson(data); } // ============================================================ // 消息 API - Tailchat 核心 chat.message 服务 // ============================================================ /// 获取会话消息。 /// 后端接口:GET /api/chat.message/fetchConverseMessage {converseId, startId?} Future> getMessages(String converseId, {int limit = 50, String? before}) async { final response = await _dio.get( '/chat.message/fetchConverseMessage', queryParameters: { 'converseId': converseId, if (before != null) 'startId': before, }); final data = _unwrapResponse(response); return (data as List).map((e) => Message.fromJson(e)).toList(); } /// 发送消息。 /// 后端接口:POST /api/chat.message/sendMessage {converseId, content, groupId?, meta?} Future sendMessage(String converseId, String content, {String? groupId}) async { final response = await _dio.post('/chat.message/sendMessage', data: { 'converseId': converseId, 'content': content, if (groupId != null) 'groupId': groupId, }); final data = _unwrapResponse(response); return Message.fromJson(data); } /// 撤回消息(2 分钟内)。 /// 后端接口:POST /api/chat.message/recallMessage {messageId} Future recallMessage(String messageId) async { await _dio.post('/chat.message/recallMessage', data: { 'messageId': messageId, }); } /// 删除消息(管理员)。 /// 后端接口:POST /api/chat.message/deleteMessage {messageId} Future deleteMessage(String messageId) async { await _dio.post('/chat.message/deleteMessage', data: { 'messageId': messageId, }); } // ============================================================ // 插件 API - plugin:com.msgbyte.saleschat // Tailchat 网关路由:/api/plugin:/ // ============================================================ /// 创建邀请。 Future createInvite({ required String groupId, int? expiresInDays, }) async { final data = { 'groupId': groupId, }; if (expiresInDays != null) { data['expiresIn'] = expiresInDays; } final response = await _dio.post( '/plugin:com.msgbyte.saleschat/inviteCreate', data: data); final unwrapped = _unwrapResponse(response); return Invite.fromJson(unwrapped); } /// 获取我的邀请。 Future> getMyInvites() async { final response = await _dio .get('/plugin:com.msgbyte.saleschat/inviteGetMyInvites'); final data = _unwrapResponse(response); return (data as List).map((e) => Invite.fromJson(e)).toList(); } /// 通过邀请码获取邀请(公开,无需认证)。 Future getInviteByCode(String code) async { final response = await _dio.get( '/plugin:com.msgbyte.saleschat/inviteGetByCode', queryParameters: {'code': code}); final data = _unwrapResponse(response); return Invite.fromJson(data); } /// 获取邀请统计。 Future getInviteStats(String code) async { final response = await _dio.get( '/plugin:com.msgbyte.saleschat/inviteGetStats', queryParameters: {'code': code}); final data = _unwrapResponse(response); return InviteStats.fromJson(data); } /// 停用邀请。 Future deactivateInvite(String code) async { await _dio.post( '/plugin:com.msgbyte.saleschat/inviteDeactivate', data: {'code': code}); } /// 通过邀请码加入。 Future> joinInvite(String code) async { final response = await _dio.post( '/plugin:com.msgbyte.saleschat/inviteJoin', data: {'code': code}); return _unwrapResponse(response); } /// 获取我的统计数据。 Future getMyStats() async { final response = await _dio.get('/plugin:com.msgbyte.saleschat/statsGetMyStats'); final data = _unwrapResponse(response); return MyStats.fromJson(data); } /// 获取排行榜。 Future> getRanking({String period = 'monthly'}) async { final response = await _dio.get( '/plugin:com.msgbyte.saleschat/statsGetRanking', queryParameters: {'period': period}); final data = _unwrapResponse(response); return (data as List).map((e) => RankingEntry.fromJson(e)).toList(); } /// 获取趋势数据。 Future>> getTrend({ required String salesId, String period = 'daily', }) async { final response = await _dio.get( '/plugin:com.msgbyte.saleschat/statsGetTrend', queryParameters: { 'salesId': salesId, 'period': period, }); final data = _unwrapResponse(response); return (data as List).cast>(); } /// 获取团队统计(仅管理员)。 Future>> getTeamStats() async { final response = await _dio .get('/plugin:com.msgbyte.saleschat/statsGetTeamStats'); final data = _unwrapResponse(response); return (data as List).cast>(); } /// 获取平台统计(仅超级管理员)。 Future getPlatformStats() async { final response = await _dio .get('/plugin:com.msgbyte.saleschat/statsGetPlatformStats'); final data = _unwrapResponse(response); return PlatformStats.fromJson(data); } /// 获取用户列表(仅管理员)。 Future>> getUsers() async { final response = await _dio .get('/plugin:com.msgbyte.saleschat/adminGetUserList'); final data = _unwrapResponse(response); return (data as List).cast>(); } /// 更新用户角色(仅超级管理员)。 Future updateUserRole(String userId, String role) async { await _dio.post( '/plugin:com.msgbyte.saleschat/adminUpdateUserRole', data: {'userId': userId, 'role': role}); } /// 删除用户(仅超级管理员)。 Future deleteUser(String userId, {String type = 'soft', String reason = ''}) async { await _dio.post( '/plugin:com.msgbyte.saleschat/adminDeleteUser', data: { 'userId': userId, 'type': type, if (reason.isNotEmpty) 'reason': reason, }); } /// 获取群组列表(仅管理员)。 Future>> getAdminGroups() async { final response = await _dio .get('/plugin:com.msgbyte.saleschat/adminGetGroupList'); final data = _unwrapResponse(response); return (data as List).cast>(); } /// 获取群组统计(仅管理员)。 Future> getGroupStats(String groupId) async { final response = await _dio.get( '/plugin:com.msgbyte.saleschat/adminGetGroupStats', queryParameters: {'groupId': groupId}); return _unwrapResponse(response); } /// 踢出群组成员(仅管理员)。 Future kickGroupMember(String groupId, String userId, {String? reason}) async { await _dio.post( '/plugin:com.msgbyte.saleschat/adminKickUser', data: { 'groupId': groupId, 'userId': userId, if (reason != null) 'reason': reason, }); } }