Files
system-design/software-copyright/06-writech-app-mobile/service/websocket_service.dart
T
2026-03-22 15:24:40 +08:00

407 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 自然写互动课堂手机端应用软件 V1.0
/// WebSocket实时通信服务 - 接收云端实时推送通知
///
/// 功能说明:
/// 1. WebSocket长连接管理(建立、维持、重连)
/// 2. 心跳机制(30秒间隔,检测连接存活性)
/// 3. 消息类型分发(新作业、批改完成、课堂互动、家校消息)
/// 4. 指数退避重连策略(断线后自动重连,逐步增加间隔)
/// 5. 消息ACK确认(确保重要消息不丢失)
/// 6. 离线消息补发(重连后请求离线期间的消息)
import 'dart:async';
import 'dart:convert';
/* ========== 消息类型定义 ========== */
/// WebSocket消息类型枚举
enum WsMessageType {
heartbeat, // 心跳包
heartbeatAck, // 心跳响应
newAssignment, // 新作业通知
gradeComplete, // 批改完成通知
classroomEvent, // 课堂互动事件(发题/收卷等)
parentMessage, // 家校沟通消息
systemNotice, // 系统公告
strokeRealtime, // 实时笔迹数据(课堂模式)
offlineSync, // 离线消息同步
ack, // 消息确认
}
/// WebSocket消息模型
class WsMessage {
final String id; // 消息唯一ID
final WsMessageType type; // 消息类型
final Map<String, dynamic> data; // 消息内容
final int timestamp; // 服务端时间戳
final bool requireAck; // 是否需要ACK确认
WsMessage({
required this.id,
required this.type,
required this.data,
required this.timestamp,
this.requireAck = false,
});
/// 从JSON反序列化
factory WsMessage.fromJson(Map<String, dynamic> json) {
return WsMessage(
id: json['id'] ?? '',
type: _parseMessageType(json['type'] ?? ''),
data: Map<String, dynamic>.from(json['data'] ?? {}),
timestamp: json['timestamp'] ?? 0,
requireAck: json['require_ack'] ?? false,
);
}
/// 序列化为JSON
Map<String, dynamic> toJson() => {
'id': id,
'type': type.name,
'data': data,
'timestamp': timestamp,
};
/// 解析消息类型字符串
static WsMessageType _parseMessageType(String typeStr) {
switch (typeStr) {
case 'heartbeat': return WsMessageType.heartbeat;
case 'heartbeat_ack': return WsMessageType.heartbeatAck;
case 'new_assignment': return WsMessageType.newAssignment;
case 'grade_complete': return WsMessageType.gradeComplete;
case 'classroom_event': return WsMessageType.classroomEvent;
case 'parent_message': return WsMessageType.parentMessage;
case 'system_notice': return WsMessageType.systemNotice;
case 'stroke_realtime': return WsMessageType.strokeRealtime;
case 'offline_sync': return WsMessageType.offlineSync;
case 'ack': return WsMessageType.ack;
default: return WsMessageType.systemNotice;
}
}
}
/* ========== WebSocket连接状态 ========== */
/// 连接状态枚举
enum WsConnectionState {
disconnected, // 未连接
connecting, // 正在连接
connected, // 已连接
reconnecting, // 重连中
}
/* ========== WebSocket服务实现 ========== */
/// WebSocket实时通信服务
/// 维护与云平台的长连接,接收实时推送通知
class WebSocketService {
/// WebSocket服务器地址
static const String _wsUrl = 'wss://ws.writech.com/v1/notify';
/// 心跳间隔(秒)
static const int heartbeatIntervalSec = 30;
/// 心跳超时时间(秒,超过此时间未收到心跳响应则认为连接断开)
static const int heartbeatTimeoutSec = 45;
/// 最大重连间隔(秒,指数退避上限)
static const int maxReconnectIntervalSec = 60;
/// WebSocket实例
dynamic _webSocket; // WebSocket
/// 连接状态
WsConnectionState _state = WsConnectionState.disconnected;
/// 当前认证Token
String _authToken = '';
/// 心跳定时器
Timer? _heartbeatTimer;
/// 心跳超时定时器
Timer? _heartbeatTimeoutTimer;
/// 重连定时器
Timer? _reconnectTimer;
/// 当前重连尝试次数(用于指数退避计算)
int _reconnectAttempts = 0;
/// 最后收到消息的时间戳(用于离线消息补发)
int _lastMessageTimestamp = 0;
/// 消息分发回调注册表
final Map<WsMessageType, List<Function(WsMessage)>> _handlers = {};
/// 连接状态变化回调
final List<Function(WsConnectionState)> _stateListeners = [];
/// 待ACK的消息队列(消息ID -> 超时Timer
final Map<String, Timer> _pendingAcks = {};
/// 获取当前连接状态
WsConnectionState get state => _state;
/// 设置认证Token(登录成功后调用)
void setAuthToken(String token) {
_authToken = token;
}
/// 注册消息处理器
/// 同一类型可注册多个处理器,按注册顺序依次执行
void on(WsMessageType type, Function(WsMessage) handler) {
_handlers.putIfAbsent(type, () => []);
_handlers[type]!.add(handler);
}
/// 移除消息处理器
void off(WsMessageType type, Function(WsMessage) handler) {
_handlers[type]?.remove(handler);
}
/// 监听连接状态变化
void onStateChange(Function(WsConnectionState) listener) {
_stateListeners.add(listener);
}
/// 建立WebSocket连接
/// 附带认证Token和最后消息时间戳(用于离线消息补发)
Future<void> connect() async {
if (_state == WsConnectionState.connected || _state == WsConnectionState.connecting) {
return;
}
_updateState(WsConnectionState.connecting);
try {
// 构造带认证参数的WebSocket URL
final url = '$_wsUrl?token=$_authToken&last_ts=$_lastMessageTimestamp';
// 建立WebSocket连接
// 实际实现: _webSocket = await WebSocket.connect(url);
print('[WebSocket] 正在连接: $_wsUrl');
// 模拟连接成功
await Future.delayed(const Duration(milliseconds: 300));
_updateState(WsConnectionState.connected);
_reconnectAttempts = 0; // 重置重连计数
// 启动心跳机制
_startHeartbeat();
// 监听消息流
// _webSocket.listen(_onMessage, onDone: _onDisconnected, onError: _onError);
print('[WebSocket] 连接成功');
} catch (e) {
print('[WebSocket] 连接失败: $e');
_updateState(WsConnectionState.disconnected);
_scheduleReconnect();
}
}
/// 处理接收到的WebSocket消息
void _onMessage(dynamic rawData) {
try {
final json = jsonDecode(rawData as String) as Map<String, dynamic>;
final message = WsMessage.fromJson(json);
// 更新最后消息时间戳
if (message.timestamp > _lastMessageTimestamp) {
_lastMessageTimestamp = message.timestamp;
}
// 处理心跳响应
if (message.type == WsMessageType.heartbeatAck) {
_onHeartbeatAck();
return;
}
// 处理ACK确认
if (message.type == WsMessageType.ack) {
_onAckReceived(message.data['ack_id'] ?? '');
return;
}
// 如果消息需要ACK,发送确认
if (message.requireAck) {
_sendAck(message.id);
}
// 分发消息到注册的处理器
_dispatchMessage(message);
} catch (e) {
print('[WebSocket] 消息解析失败: $e');
}
}
/// 分发消息到对应类型的处理器
void _dispatchMessage(WsMessage message) {
final handlers = _handlers[message.type];
if (handlers != null && handlers.isNotEmpty) {
for (final handler in handlers) {
try {
handler(message);
} catch (e) {
print('[WebSocket] 消息处理器异常: $e');
}
}
} else {
print('[WebSocket] 未注册的消息类型: ${message.type}');
}
}
/// 发送消息确认(ACK
void _sendAck(String messageId) {
_send({
'type': 'ack',
'data': {'ack_id': messageId},
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
/// 处理收到的ACK确认
void _onAckReceived(String messageId) {
_pendingAcks[messageId]?.cancel();
_pendingAcks.remove(messageId);
}
/// 启动心跳机制
/// 每30秒发送一次心跳包,45秒内未收到响应则断开重连
void _startHeartbeat() {
_stopHeartbeat();
_heartbeatTimer = Timer.periodic(
Duration(seconds: heartbeatIntervalSec),
(_) => _sendHeartbeat(),
);
}
/// 发送心跳包
void _sendHeartbeat() {
_send({
'type': 'heartbeat',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
// 设置心跳超时检测
_heartbeatTimeoutTimer?.cancel();
_heartbeatTimeoutTimer = Timer(
Duration(seconds: heartbeatTimeoutSec),
() {
print('[WebSocket] 心跳超时,断开连接');
_onDisconnected();
},
);
}
/// 收到心跳响应,取消超时计时器
void _onHeartbeatAck() {
_heartbeatTimeoutTimer?.cancel();
}
/// 停止心跳
void _stopHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = null;
_heartbeatTimeoutTimer?.cancel();
_heartbeatTimeoutTimer = null;
}
/// 发送JSON数据
void _send(Map<String, dynamic> data) {
if (_state != WsConnectionState.connected) return;
try {
final jsonStr = jsonEncode(data);
// 实际调用: _webSocket.add(jsonStr);
print('[WebSocket] 发送: ${data['type']}');
} catch (e) {
print('[WebSocket] 发送失败: $e');
}
}
/// 连接断开处理
void _onDisconnected() {
_stopHeartbeat();
_updateState(WsConnectionState.disconnected);
print('[WebSocket] 连接已断开');
_scheduleReconnect();
}
/// 连接错误处理
void _onError(dynamic error) {
print('[WebSocket] 连接错误: $error');
_onDisconnected();
}
/// 安排自动重连(指数退避策略)
/// 间隔: 1s, 2s, 4s, 8s, 16s, 32s, 60s(上限)
void _scheduleReconnect() {
_reconnectTimer?.cancel();
final interval = _calculateReconnectInterval();
_updateState(WsConnectionState.reconnecting);
print('[WebSocket] ${interval}秒后尝试重连 (第${_reconnectAttempts + 1}次)');
_reconnectTimer = Timer(Duration(seconds: interval), () {
_reconnectAttempts++;
connect();
});
}
/// 计算重连间隔(指数退避,上限60秒)
int _calculateReconnectInterval() {
final interval = 1 << _reconnectAttempts; // 2^n
return interval > maxReconnectIntervalSec ? maxReconnectIntervalSec : interval;
}
/// 更新连接状态并通知监听器
void _updateState(WsConnectionState newState) {
if (_state == newState) return;
_state = newState;
for (final listener in _stateListeners) {
try {
listener(newState);
} catch (e) {
print('[WebSocket] 状态监听器异常: $e');
}
}
}
/// 主动重连(应用前台恢复时调用)
void reconnect() {
if (_state == WsConnectionState.connected) return;
_reconnectAttempts = 0;
connect();
}
/// 断开连接并释放资源
void disconnect() {
_reconnectTimer?.cancel();
_reconnectTimer = null;
_stopHeartbeat();
// 取消所有待ACK的超时计时器
for (final timer in _pendingAcks.values) {
timer.cancel();
}
_pendingAcks.clear();
// 关闭WebSocket连接
// 实际调用: _webSocket?.close();
_webSocket = null;
_updateState(WsConnectionState.disconnected);
print('[WebSocket] 已主动断开连接');
}
/// 销毁服务(释放所有资源和回调)
void dispose() {
disconnect();
_handlers.clear();
_stateListeners.clear();
}
}