diff --git a/lib/common/models/mq/mq_enum_models.dart b/lib/common/models/mq/mq_enum_models.dart index cf87377..ac13d18 100644 --- a/lib/common/models/mq/mq_enum_models.dart +++ b/lib/common/models/mq/mq_enum_models.dart @@ -8,6 +8,7 @@ enum MqtActionEnum implements BaseEnum { SUPERMARKET_CLASSIFY("50", "超市分类变化"), PRINT("60", "打印变化"), FACE("70", "人脸变化"), + TAKE_FOOD_BROADCAST("80", "定餐广播"), ; @override diff --git a/lib/common/models/mq/mq_models.dart b/lib/common/models/mq/mq_models.dart index 6b98dce..47355af 100644 --- a/lib/common/models/mq/mq_models.dart +++ b/lib/common/models/mq/mq_models.dart @@ -61,3 +61,54 @@ class MqttInvoiceMsg { Map toJson() => _$MqttInvoiceMsgToJson(this); } + +@JsonSerializable() +class MqttTakeFoodBroadcastMsg { + /// 发送硬件的topic + String topic; + + /// 用户id + String userId; + + /// 用户id + String username; + + /// 用户id + String userMobile; + + /// 广播id + String id; + + /// 预定标题 + String title; + + /// 广播内容 + String content; + + /// 最大广播次数 + int broadcastMaxCount; + + /// 商品介绍 + String productDesc; + + /// 支付价格 + int payPrice; + + MqttTakeFoodBroadcastMsg({ + required this.topic, + required this.userId, + required this.username, + required this.userMobile, + required this.id, + required this.title, + required this.content, + required this.broadcastMaxCount, + required this.productDesc, + required this.payPrice, + }); + + factory MqttTakeFoodBroadcastMsg.fromJson(Map json) => + _$MqttTakeFoodBroadcastMsgFromJson(json); + + Map toJson() => _$MqttTakeFoodBroadcastMsgToJson(this); +} diff --git a/lib/common/models/mq/mq_models.g.dart b/lib/common/models/mq/mq_models.g.dart index ea42f4e..755bb14 100644 --- a/lib/common/models/mq/mq_models.g.dart +++ b/lib/common/models/mq/mq_models.g.dart @@ -22,6 +22,7 @@ const _$MqtActionEnumEnumMap = { MqtActionEnum.SUPERMARKET_CLASSIFY: 'SUPERMARKET_CLASSIFY', MqtActionEnum.PRINT: 'PRINT', MqtActionEnum.FACE: 'FACE', + MqtActionEnum.TAKE_FOOD_BROADCAST: 'TAKE_FOOD_BROADCAST', }; MqttInvoiceMsg _$MqttInvoiceMsgFromJson(Map json) => @@ -45,3 +46,33 @@ Map _$MqttInvoiceMsgToJson(MqttInvoiceMsg instance) => 'pic': instance.pic, 'isGlobalFace': instance.isGlobalFace, }; + +MqttTakeFoodBroadcastMsg _$MqttTakeFoodBroadcastMsgFromJson( + Map json) => + MqttTakeFoodBroadcastMsg( + topic: json['topic'] as String, + userId: json['userId'] as String, + username: json['username'] as String, + userMobile: json['userMobile'] as String, + id: json['id'] as String, + title: json['title'] as String, + content: json['content'] as String, + broadcastMaxCount: (json['broadcastMaxCount'] as num).toInt(), + productDesc: json['productDesc'] as String, + payPrice: (json['payPrice'] as num).toInt(), + ); + +Map _$MqttTakeFoodBroadcastMsgToJson( + MqttTakeFoodBroadcastMsg instance) => + { + 'topic': instance.topic, + 'userId': instance.userId, + 'username': instance.username, + 'userMobile': instance.userMobile, + 'id': instance.id, + 'title': instance.title, + 'content': instance.content, + 'broadcastMaxCount': instance.broadcastMaxCount, + 'productDesc': instance.productDesc, + 'payPrice': instance.payPrice, + }; diff --git a/lib/common/services/database/dao/broadcast_dao.dart b/lib/common/services/database/dao/broadcast_dao.dart new file mode 100644 index 0000000..e2fba6b --- /dev/null +++ b/lib/common/services/database/dao/broadcast_dao.dart @@ -0,0 +1,23 @@ +import 'package:floor/floor.dart'; + +import 'package:pj_supermarket_cloud/common/index.dart'; + +// broadcast +@dao +abstract class BroadcastDao { + @insert + Future save(BroadcastEntity entity); + + @insert + Future batchSave(List entitys); + + @Query(""" + SELECT * FROM broadcast + WHERE + isDone = 0 + AND enable = true + AND broadcastCount < broadcastMaxCount + AND strftime('%Y-%m-%d', createTime) = strftime('%Y-%m-%d', 'now', 'localtime') + """) + Future> getAll(); +} diff --git a/lib/common/services/database/database.dart b/lib/common/services/database/database.dart index dee1237..3bb8329 100644 --- a/lib/common/services/database/database.dart +++ b/lib/common/services/database/database.dart @@ -8,6 +8,7 @@ import 'package:sqflite/sqflite.dart' as sqflite; part 'database.g.dart'; const entities = [ + BroadcastEntity, OrderEntity, CanteenSpuEntity, SupermarketSkuEntity, @@ -37,6 +38,7 @@ const typeConverters = [ @Database(version: 1, entities: entities) @TypeConverters(typeConverters) abstract class AppDatabase extends FloorDatabase { + BroadcastDao get getBroadcastDao; OrderDao get getOrderDao; CanteenSpuDao get getCanteenSpuDao; SupermarketSkuDao get getSupermarketSkuDao; diff --git a/lib/common/services/database/database.g.dart b/lib/common/services/database/database.g.dart index e72bb86..9083bdf 100644 --- a/lib/common/services/database/database.g.dart +++ b/lib/common/services/database/database.g.dart @@ -72,6 +72,8 @@ class _$AppDatabase extends AppDatabase { changeListener = listener ?? StreamController.broadcast(); } + BroadcastDao? _getBroadcastDaoInstance; + OrderDao? _getOrderDaoInstance; CanteenSpuDao? _getCanteenSpuDaoInstance; @@ -115,6 +117,8 @@ class _$AppDatabase extends AppDatabase { await callback?.onUpgrade?.call(database, startVersion, endVersion); }, onCreate: (database, version) async { + await database.execute( + 'CREATE TABLE IF NOT EXISTS `broadcast` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `content` TEXT NOT NULL, `broadcastCount` INTEGER NOT NULL, `broadcastMaxCount` INTEGER NOT NULL, `isBroadcast` INTEGER NOT NULL, `isDone` INTEGER NOT NULL, `isDoneTime` TEXT, `serviceCode` TEXT NOT NULL, `userId` TEXT NOT NULL, `username` TEXT NOT NULL, `userMobile` TEXT NOT NULL, `productDesc` TEXT NOT NULL, `payPrice` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `createTime` TEXT NOT NULL)'); await database.execute( 'CREATE TABLE IF NOT EXISTS `order_record` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `uniqueCode` TEXT NOT NULL, `serviceUniqueCode` TEXT NOT NULL, `sysType` TEXT NOT NULL, `userCode` TEXT NOT NULL, `username` TEXT, `hourType` TEXT, `price` INTEGER NOT NULL, `extraPrice` INTEGER NOT NULL, `payPrice` INTEGER NOT NULL, `walletId` TEXT, `orderSourceTypeId` TEXT, `type` TEXT NOT NULL, `status` TEXT NOT NULL, `payType` TEXT NOT NULL, `productDesc` TEXT, `count` INTEGER NOT NULL, `weight` REAL NOT NULL, `payTime` TEXT, `createTime` TEXT NOT NULL)'); await database.execute( @@ -144,6 +148,12 @@ class _$AppDatabase extends AppDatabase { return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions); } + @override + BroadcastDao get getBroadcastDao { + return _getBroadcastDaoInstance ??= + _$BroadcastDao(database, changeListener); + } + @override OrderDao get getOrderDao { return _getOrderDaoInstance ??= _$OrderDao(database, changeListener); @@ -205,6 +215,71 @@ class _$AppDatabase extends AppDatabase { } } +class _$BroadcastDao extends BroadcastDao { + _$BroadcastDao( + this.database, + this.changeListener, + ) : _queryAdapter = QueryAdapter(database), + _broadcastEntityInsertionAdapter = InsertionAdapter( + database, + 'broadcast', + (BroadcastEntity item) => { + 'id': item.id, + 'content': item.content, + 'broadcastCount': item.broadcastCount, + 'broadcastMaxCount': item.broadcastMaxCount, + 'isBroadcast': _boolConverter.encode(item.isBroadcast), + 'isDone': _boolConverter.encode(item.isDone), + 'isDoneTime': _dateTimeEmptyConverter.encode(item.isDoneTime), + 'serviceCode': item.serviceCode, + 'userId': item.userId, + 'username': item.username, + 'userMobile': item.userMobile, + 'productDesc': item.productDesc, + 'payPrice': item.payPrice, + 'enable': _boolConverter.encode(item.enable), + 'createTime': _dateTimeConverter.encode(item.createTime) + }); + + final sqflite.DatabaseExecutor database; + + final StreamController changeListener; + + final QueryAdapter _queryAdapter; + + final InsertionAdapter _broadcastEntityInsertionAdapter; + + @override + Future> getAll() async { + return _queryAdapter.queryList( + 'SELECT * FROM broadcast WHERE isDone = 0 AND enable = true AND broadcastCount < broadcastMaxCount AND strftime(\'%Y-%m-%d\', createTime) = strftime(\'%Y-%m-%d\', \'now\', \'localtime\')', + mapper: (Map row) => BroadcastEntity( + id: row['id'] as int?, + content: row['content'] as String, + broadcastMaxCount: row['broadcastMaxCount'] as int, + isBroadcast: _boolConverter.decode(row['isBroadcast'] as int), + createTime: _dateTimeConverter.decode(row['createTime'] as String), + serviceCode: row['serviceCode'] as String, + userId: row['userId'] as String, + username: row['username'] as String, + userMobile: row['userMobile'] as String, + productDesc: row['productDesc'] as String, + payPrice: row['payPrice'] as int)); + } + + @override + Future save(BroadcastEntity entity) async { + await _broadcastEntityInsertionAdapter.insert( + entity, OnConflictStrategy.abort); + } + + @override + Future batchSave(List entitys) async { + await _broadcastEntityInsertionAdapter.insertList( + entitys, OnConflictStrategy.abort); + } +} + class _$OrderDao extends OrderDao { _$OrderDao( this.database, diff --git a/lib/common/services/database/entity/broadcast_entity.dart b/lib/common/services/database/entity/broadcast_entity.dart index da10a2a..91c867b 100644 --- a/lib/common/services/database/entity/broadcast_entity.dart +++ b/lib/common/services/database/entity/broadcast_entity.dart @@ -28,6 +28,27 @@ class BroadcastEntity { /// 完成时间 DateTime? isDoneTime; + /// 服务单号 + String serviceCode; + + /// 用户id + String userId; + + /// 用户姓名 + String username; + + /// 用户手机号 + String userMobile; + + /// 商品介绍 + String productDesc; + + /// 支付价格 + int payPrice; + + /// 手动开关 + bool enable = true; + /// 创建时间 DateTime createTime; @@ -37,6 +58,12 @@ class BroadcastEntity { required this.broadcastMaxCount, required this.isBroadcast, required this.createTime, + required this.serviceCode, + required this.userId, + required this.username, + required this.userMobile, + required this.productDesc, + required this.payPrice, }); factory BroadcastEntity.fromJson(Map json) => diff --git a/lib/common/services/database/entity/broadcast_entity.g.dart b/lib/common/services/database/entity/broadcast_entity.g.dart index 6ab04dc..b6a4a0b 100644 --- a/lib/common/services/database/entity/broadcast_entity.g.dart +++ b/lib/common/services/database/entity/broadcast_entity.g.dart @@ -13,12 +13,19 @@ BroadcastEntity _$BroadcastEntityFromJson(Map json) => broadcastMaxCount: (json['broadcastMaxCount'] as num).toInt(), isBroadcast: json['isBroadcast'] as bool, createTime: DateTime.parse(json['createTime'] as String), + serviceCode: json['serviceCode'] as String, + userId: json['userId'] as String, + username: json['username'] as String, + userMobile: json['userMobile'] as String, + productDesc: json['productDesc'] as String, + payPrice: (json['payPrice'] as num).toInt(), ) ..broadcastCount = (json['broadcastCount'] as num).toInt() ..isDone = json['isDone'] as bool ..isDoneTime = json['isDoneTime'] == null ? null - : DateTime.parse(json['isDoneTime'] as String); + : DateTime.parse(json['isDoneTime'] as String) + ..enable = json['enable'] as bool; Map _$BroadcastEntityToJson(BroadcastEntity instance) => { @@ -29,5 +36,12 @@ Map _$BroadcastEntityToJson(BroadcastEntity instance) => 'isBroadcast': instance.isBroadcast, 'isDone': instance.isDone, 'isDoneTime': instance.isDoneTime?.toIso8601String(), + 'serviceCode': instance.serviceCode, + 'userId': instance.userId, + 'username': instance.username, + 'userMobile': instance.userMobile, + 'productDesc': instance.productDesc, + 'payPrice': instance.payPrice, + 'enable': instance.enable, 'createTime': instance.createTime.toIso8601String(), }; diff --git a/lib/common/services/index.dart b/lib/common/services/index.dart index 90d86e7..571bc1e 100644 --- a/lib/common/services/index.dart +++ b/lib/common/services/index.dart @@ -4,6 +4,7 @@ export './database/database.dart'; export './database/type_converter/type_converter.dart'; +export './database/dao/broadcast_dao.dart'; export './database/dao/classify_dao.dart'; export './database/dao/face_dao.dart'; export './database/dao/order_dao.dart'; @@ -16,6 +17,7 @@ export './database/dao/supermarket_sku_dao.dart'; export './database/dao/supermarket_spu_dao.dart'; export './database/dao/user_dao.dart'; +export './database/entity/broadcast_entity.dart'; export './database/entity/classify_entity.dart'; export './database/entity/face_entity.dart'; export './database/entity/order_entity.dart'; diff --git a/lib/common/utils/app/app_mq_tool.dart b/lib/common/utils/app/app_mq_tool.dart index d3302cf..4855b49 100644 --- a/lib/common/utils/app/app_mq_tool.dart +++ b/lib/common/utils/app/app_mq_tool.dart @@ -112,6 +112,11 @@ class AppMqTool { ); } break; + case MqtActionEnum.TAKE_FOOD_BROADCAST: + mqUpdateEventBus.fire(MqtActionEnum.TAKE_FOOD_BROADCAST); + final msg = MqttTakeFoodBroadcastMsg.fromJson(json.decode(orgStr)); + SyncTakeFoodBroadcastTool.addTask(msg); + return; case MqtActionEnum.FACE: return; // final msg = MqttInvoiceMsg.fromJson(json.decode(orgStr)); diff --git a/lib/common/utils/index.dart b/lib/common/utils/index.dart index b161811..bf12768 100644 --- a/lib/common/utils/index.dart +++ b/lib/common/utils/index.dart @@ -41,4 +41,5 @@ export './sync/sync_face_tool.dart'; export './sync/sync_order_tool.dart'; export './sync/sync_print_tool.dart'; export './sync/sync_product_tool.dart'; +export './sync/sync_take_food_broadcast_tool.dart'; export './sync/sync_user_tool.dart'; diff --git a/lib/common/utils/sync/sync_check_tool.dart b/lib/common/utils/sync/sync_check_tool.dart index 350af42..203270c 100644 --- a/lib/common/utils/sync/sync_check_tool.dart +++ b/lib/common/utils/sync/sync_check_tool.dart @@ -21,10 +21,14 @@ class SyncCheckTool { Duration printInterval = const Duration(seconds: 60 * 10); + Duration broadcastInterval = const Duration(seconds: 60 * 10); + Timer? mqTimer; Timer? printTimer; + Timer? broadcastTimer; + // ************************************************************************** // 定时器配置 end // ************************************************************************** @@ -33,6 +37,7 @@ class SyncCheckTool { if (_isRunning) return; checkMq(); checkPrint(); + checkBroadcast(); } void dispose() { @@ -94,4 +99,30 @@ class SyncCheckTool { } }); } + + // 定时检查需不要广播 + checkBroadcast() { + String logMsg = "定时检查需不要广播"; + final inSeconds = broadcastInterval.inSeconds; + if (broadcastTimer != null && broadcastTimer!.isActive) { + broadcastTimer?.cancel(); + broadcastTimer = null; + } + if (broadcastTimer != null) { + broadcastTimer?.cancel(); + broadcastTimer = null; + } + broadcastTimer = Timer.periodic(broadcastInterval, (Timer timer) async { + final isNetwork = AppNetworkTool.isAutoNetwork(); + if (!isNetwork) return; + logger.i("|$_tag|$logMsg,$inSeconds秒一次,开始执行"); + try { + await SyncTakeFoodBroadcastTool.doCheckBroadcast(); + } catch (e) { + logger.i("|$_tag|$logMsg,$inSeconds秒一次,执行失败,error:$e"); + } finally { + logger.i("|$_tag|$logMsg,$inSeconds秒一次,执行完成"); + } + }); + } } diff --git a/lib/common/utils/sync/sync_take_food_broadcast_tool.dart b/lib/common/utils/sync/sync_take_food_broadcast_tool.dart new file mode 100644 index 0000000..1617226 --- /dev/null +++ b/lib/common/utils/sync/sync_take_food_broadcast_tool.dart @@ -0,0 +1,77 @@ +import 'package:common_utils/common_utils.dart'; +import 'package:get/get.dart'; + +import 'package:pj_supermarket_cloud/common/index.dart'; + +abstract class SyncTakeFoodBroadcastTool { + static const _tag = "sync订餐广播工具"; + + static bool _isBroadcast = false; + + static Future addTask(MqttTakeFoodBroadcastMsg msg) async { + final appC = Get.find(); + final broadcastDao = appC.dataBase.getBroadcastDao; + final saveObj = BroadcastEntity( + content: msg.content, + broadcastMaxCount: msg.broadcastMaxCount, + isBroadcast: true, + productDesc: msg.productDesc, + serviceCode: msg.id, + userId: msg.userId, + username: msg.username, + userMobile: msg.userMobile, + payPrice: msg.payPrice, + createTime: DateTime.now(), + ); + await broadcastDao.save(saveObj); + logger.i("|$_tag|添加到打印记录到本地数据库,json:|${saveObj.toJson()}|"); + await SyncTakeFoodBroadcastTool.doCheckBroadcast(); + } + + // 检查广播 + static Future doCheckBroadcast() async { + if (_isBroadcast) { + logger.i("|$_tag|检查打印,正在广播中"); + return; + } + _isBroadcast = true; + final appC = Get.find(); + final broadcastDao = appC.dataBase.getBroadcastDao; + try { + final list = await broadcastDao.getAll(); + if (ObjectUtil.isEmptyList(list)) { + _isBroadcast = false; + logger.i("|$_tag|检查广播,没有需要广播的订单信息"); + return; + } + // 将列表分组,每组5个元素 + final int groupSize = 5; + final int totalGroups = (list.length / groupSize).ceil(); + for (int i = 0; i < totalGroups; i++) { + // 计算当前组的起始和结束索引 + final int start = i * groupSize; + final int end = (start + groupSize) < list.length + ? (start + groupSize) + : list.length; + // 获取当前组的元素 + final currentGroup = list.sublist(start, end); + String msg = "请手机尾号,"; + for (final v in currentGroup) { + msg += "${v.content},"; + } + msg = msg.substring(0, msg.length - 1); + msg += "的用户,请尽快取餐"; + // 播报当前组 + await TtsManage.speak(msg); + // 如果不是最后一组,等待一段时间再播报下一组 + if (i < totalGroups - 1) { + await Future.delayed(const Duration(seconds: 3)); + } + } + } catch (e, stack) { + logger.e("|$_tag|检查广播,广播错误,error:|$e|stack:|$stack|"); + } finally { + _isBroadcast = false; + } + } +}