Files
biz_xfj_cloud/lib/common/utils/manage/mq_manager.dart
T
2026-07-24 15:27:20 +08:00

193 lines
6.1 KiB
Dart

import 'dart:io';
import 'package:common_utils/common_utils.dart';
import 'package:flutter/services.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
/// mq
class MqManager {
static const String _tag = "mq管理";
static MqManager? _instance;
// mq对象
static MqttServerClient? mqttClient;
static MqttConnectionState state = MqttConnectionState.disconnected;
static Future<void>? _refreshFuture;
static String? connectionFailureReason;
// 当前设备的 topic
static late String mainTopic;
MqManager._internal();
factory MqManager() {
_instance ??= MqManager._internal();
return _instance!;
}
static Future<SysSetting?> validate() async {
final config = await AppSettingTool.getSysSetting();
if (ObjectUtil.isEmpty(config)) return null;
if (ObjectUtil.isEmpty(config.mqUsername)) return null;
if (ObjectUtil.isEmpty(config.mqPassword)) return null;
if (ObjectUtil.isEmpty(config.mqClientId)) return null;
if (ObjectUtil.isEmpty(config.mqSocketAddress)) return null;
if (ObjectUtil.isEmpty(config.mqSocketPort)) return null;
if (ObjectUtil.isEmpty(config.mainTopic)) return null;
// 只记录非敏感连接信息,避免账号、密码等配置进入终端日志。
logger.i(
"|$_tag|mq配置校验通过,地址:${config.mqSocketAddress},端口:${config.mqSocketPort}",
);
return config;
}
static Future<void> closeMqtt() async {
if (MqManager.mqttClient != null) {
MqManager.mqttClient?.disconnect();
MqManager.mqttClient = null;
}
MqManager.state = MqttConnectionState.disconnected;
connectionFailureReason = null;
}
static Future<void> initMqtt() async {
// 获取配置
final config = await validate();
if (config == null) return;
final port = int.tryParse(config.mqSocketPort!);
if (ObjectUtil.isEmpty(port)) return;
final client = MqttServerClient.withPort(
config.mqSocketAddress!,
config.mqClientId!,
port!,
);
mqttClient = client;
if (port == 8883) {
final context = SecurityContext.defaultContext;
final byte = await rootBundle.load(AssetsCrt.emqxCa);
context.setClientAuthoritiesBytes(byte.buffer.asUint8List());
client.securityContext = context;
client.secure = true;
}
client.onBadCertificate = (Object a) => true;
client.autoReconnect = false;
client.logging(on: false);
client.keepAlivePeriod = 60;
client.onConnected = _onConnected;
client.onDisconnected = _onDisconnected;
client.onSubscribed = _onSubscribed;
client.pongCallback = _pongCallback;
client.onAutoReconnect = _onAutoReconnect;
final connMessage =
MqttConnectMessage().withProtocolName("MQTT").withProtocolVersion(4);
client.connectionMessage = connMessage;
try {
final result = await client.connect(config.mqUsername, config.mqPassword);
MqManager.state = result?.state ?? MqttConnectionState.disconnected;
connectionFailureReason =
MqManager.state == MqttConnectionState.connected ? null : '消息服务未建立连接';
} catch (e, stack) {
_recordConnectionFailure(e, stack);
}
}
static Future<void> refresh() async {
final refreshing = _refreshFuture;
if (refreshing != null) return refreshing;
final future = _refresh();
_refreshFuture = future;
try {
await future;
} finally {
if (identical(_refreshFuture, future)) _refreshFuture = null;
}
}
/// 网络监听与启动流程可能同时触发重连,串行执行可避免同一客户端并发 connect。
static Future<void> _refresh() async {
final config = await validate();
if (ObjectUtil.isEmpty(config)) {
logger.e("|$_tag||刷新mq|错误mq参数校验不通过");
return;
}
if (ObjectUtil.isEmpty(mqttClient)) {
await initMqtt();
} else if (MqManager.state != MqttConnectionState.connected) {
try {
final result = await mqttClient!.connect(
config!.mqUsername,
config.mqPassword,
);
MqManager.state = result?.state ?? MqttConnectionState.disconnected;
connectionFailureReason =
MqManager.state == MqttConnectionState.connected
? null
: '消息服务未建立连接';
} catch (e, stack) {
_recordConnectionFailure(e, stack);
}
}
}
static Future<void> subscribeCurrentTopic() async {
if (ObjectUtil.isEmpty(mqttClient)) return;
final config = await validate();
if (ObjectUtil.isEmpty(config)) return;
if (ObjectUtil.isEmpty(config?.mainTopic)) return;
mqttClient!.subscribe(config!.mainTopic!, MqttQos.atMostOnce);
}
static void _onConnected() async {
logger.w("|$_tag|mqtt连接成功");
connectionFailureReason = null;
final config = await AppSettingTool.getSysSetting();
if (ObjectUtil.isNotEmpty(config)) {
config.mqSyncTime = DateTime.now();
await AppSettingTool.setSysSetting(config);
}
await subscribeCurrentTopic();
}
static void _onDisconnected() async {
logger.w("|$_tag|mqtt断开链接");
AppMqTool.mqListenClose();
state = MqttConnectionState.disconnected;
}
static void _onSubscribed(String topic) {
logger.w('|$_tag|mqtt 订阅主题成功:$topic');
mainTopic = topic;
}
static void _pongCallback() {
logger.w("|$_tag|mqtt ping");
}
// 自动重连接
static void _onAutoReconnect() async {
logger.i("|$_tag|启动自动重连");
await refresh();
}
static void _recordConnectionFailure(Object error, StackTrace stack) {
state = MqttConnectionState.disconnected;
final message = error.toString().toLowerCase();
connectionFailureReason = message.contains('connection refused')
? '消息服务拒绝连接'
: message.contains('timed out') || message.contains('timeout')
? '消息服务连接超时'
: error is SocketException
? '消息服务地址不可达'
: '消息服务连接失败';
logger.e("|$_tag|mqtt连接失败error:|$error|stack:|$stack|");
}
}