import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:get_it/get_it.dart'; import '../../../../core/consts/http_api_consts.dart'; import '../../../../core/logging/i_logger_service.dart'; import '../../domain/entities/device_schedule_plan_entity.dart'; import '../models/device_task_model.dart'; abstract class DeviceTaskDatasource { Future> getDeviceTaskPool({ required String userId, required int siteId, required int orgId, int pageNum = 1, int pageSize = 99999999, }); Future cancelTask({ required String deviceId, required int taskId, required int orgId, required int siteId, bool forceCancel = true, }); Future pauseTask({ required String deviceId, required int taskId, required int orgId, required int siteId, }); Future> recoveryTask({ required String deviceId, required int taskId, required int orgId, required int siteId, }); /// 创建设备定时任务(定时作业计划)POST /iot/deviceTask/save /// 周期规则报文组装封在实现内,调用方只交业务实体 Future saveDeviceSchedulePlan({required DeviceSchedulePlanEntity plan}); } class DeviceTaskDatasourceImpl implements DeviceTaskDatasource { final Dio dio; final ILoggerService _logger = GetIt.I(); DeviceTaskDatasourceImpl(this.dio); @override Future> getDeviceTaskPool({ required String userId, required int siteId, required int orgId, int pageNum = 1, int pageSize = 99999999, }) async { try { final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/deviceTaskPool'; _logger.logWithLevel('[getDeviceTaskPool] 请求: POST $url', shouldLog: true); _logger.logWithLevel('[getDeviceTaskPool] 参数: userId=$userId, siteId=$siteId, orgId=$orgId, pageNum=$pageNum, pageSize=$pageSize', shouldLog: true); final response = await dio.post( url, data: { 'userId': userId, 'siteId': siteId, 'orgId': orgId, 'pageNum': pageNum, 'pageSize': pageSize, }, ); if (response.statusCode == 200) { final data = response.data as Map; if (data['code'] == 200) { final rows = data['rows'] as List? ?? []; _logger.logWithLevel('[getDeviceTaskPool] 返回任务 ${rows.length} 条', shouldLog: true); return rows .map((item) => DeviceTaskModel.fromJson(item as Map)) .toList(); } else { throw Exception(data['msg'] ?? '获取任务池失败'); } } else { throw Exception('HTTP ${response.statusCode}'); } } catch (e) { _logger.logWithLevel('❌ 获取任务池失败: $e'); rethrow; } } @override Future cancelTask({ required String deviceId, required int taskId, required int orgId, required int siteId, bool forceCancel = true, }) async { final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/cancelTask'; final body = { 'taskId': taskId, 'siteId': siteId, 'orgId': orgId, 'forceCancel': forceCancel, }; _logger.logWithLevel('[cancelTask] 请求: POST $url'); _logger.logWithLevel('[cancelTask] 参数: ${jsonEncode(body)}'); try { final response = await dio.post(url, data: body); _logger.logWithLevel('[cancelTask] 响应: statusCode=${response.statusCode}, body=${jsonEncode(response.data)}'); if (response.statusCode == 200) { final data = response.data as Map; if (data['code'] == 200) { _logger.logWithLevel('[cancelTask] 结果: 成功'); return data['data'] as bool? ?? false; } else { _logger.logWithLevel('[cancelTask] 业务失败: code=${data['code']}, msg=${data['msg']}'); throw Exception(data['msg'] ?? '取消任务失败'); } } else { throw Exception('HTTP ${response.statusCode}'); } } catch (e) { _logger.logWithLevel('[cancelTask] 异常: $e'); rethrow; } } @override Future pauseTask({ required String deviceId, required int taskId, required int orgId, required int siteId, }) async { final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/pauseTask'; final body = { 'deviceId': deviceId, 'taskId': taskId, 'orgId': orgId, 'siteId': siteId, }; _logger.logWithLevel('[pauseTask] 请求: POST $url'); _logger.logWithLevel('[pauseTask] 参数: ${jsonEncode(body)}'); try { final response = await dio.post(url, data: body); _logger.logWithLevel('[pauseTask] 响应: statusCode=${response.statusCode}, body=${jsonEncode(response.data)}'); if (response.statusCode == 200) { final data = response.data as Map; if (data['code'] == 200) { _logger.logWithLevel('[pauseTask] 结果: 成功'); return data['data'] as bool? ?? false; } else { _logger.logWithLevel('[pauseTask] 业务失败: code=${data['code']}, msg=${data['msg']}'); throw Exception(data['msg'] ?? '暂停任务失败'); } } else { throw Exception('HTTP ${response.statusCode}'); } } catch (e) { _logger.logWithLevel('[pauseTask] 异常: $e'); rethrow; } } @override Future> recoveryTask({ required String deviceId, required int taskId, required int orgId, required int siteId, }) async { final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/recoveryTask'; final body = { 'deviceId': deviceId, 'taskId': taskId, 'orgId': orgId, 'siteId': siteId, }; _logger.logWithLevel('[recoveryTask] 请求: POST $url'); _logger.logWithLevel('[recoveryTask] 参数: ${jsonEncode(body)}'); try { final response = await dio.post(url, data: body); _logger.logWithLevel('[recoveryTask] 响应: statusCode=${response.statusCode}, body=${jsonEncode(response.data)}'); if (response.statusCode == 200) { final data = response.data as Map; if (data['code'] == 200) { _logger.logWithLevel('[recoveryTask] 结果: 成功, data=${jsonEncode(data['data'])}'); return data['data'] as Map? ?? {}; } else { _logger.logWithLevel('[recoveryTask] 业务失败: code=${data['code']}, msg=${data['msg']}'); throw Exception(data['msg'] ?? '恢复任务失败'); } } else { throw Exception('HTTP ${response.statusCode}'); } } catch (e) { _logger.logWithLevel('[recoveryTask] 异常: $e'); rethrow; } } @override Future saveDeviceSchedulePlan({ required DeviceSchedulePlanEntity plan, }) async { final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/save'; final body = _schedulePlanBody(plan); _logger.logWithLevel('[saveDeviceSchedulePlan] 请求: POST $url', shouldLog: true); _logger.logWithLevel('[saveDeviceSchedulePlan] 参数: ${jsonEncode(body)}', shouldLog: true); try { final response = await dio.post(url, data: body); _logger.logWithLevel('[saveDeviceSchedulePlan] 响应: statusCode=${response.statusCode}, body=${jsonEncode(response.data)}', shouldLog: true); if (response.statusCode == 200) { final data = response.data as Map; if (data['code'] == 200) { // 保存接口返回数量(如 data: 1),同时兼容布尔值及无 data 的成功响应。 final saved = switch (data['data']) { bool value => value, num value => value > 0, _ => true, }; _logger.logWithLevel( '[saveDeviceSchedulePlan] 结果: ${saved ? '成功' : '未保存'}', shouldLog: true, ); return saved; } else { _logger.logWithLevel('[saveDeviceSchedulePlan] 业务失败: code=${data['code']}, msg=${data['msg']}', shouldLog: true); throw Exception(data['msg'] ?? '创建定时计划失败'); } } else { throw Exception('HTTP ${response.statusCode}'); } } catch (e) { _logger.logWithLevel('[saveDeviceSchedulePlan] 异常: $e', shouldLog: true); rethrow; } } /// 业务实体 → 后端报文:字段名 / DAY・WEEK・MONTH 枚举字面量 / /// days・dates 条件结构 / 时间格式,全部封在数据层,不外泄到 domain。 static Map _schedulePlanBody(DeviceSchedulePlanEntity p) { // 下标 = weekday.value - 1(周一~周日) const List weekdayWire = [ 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY', ]; String pad(int value) => value.toString().padLeft(2, '0'); String dateText(DateTime d) => '${d.year}-${pad(d.month)}-${pad(d.day)}'; return { 'deviceId': p.deviceId, 'orgId': p.orgId, 'planName': p.planName.trim(), 'routeId': p.routeId, 'siteId': p.siteId, 'deviceTaskPlanRules': { 'type': switch (p.type) { WorkScheduleType.day => 'DAY', WorkScheduleType.week => 'WEEK', WorkScheduleType.month => 'MONTH', }, if (p.type == WorkScheduleType.week) 'days': [ for (final Weekday w in p.days) weekdayWire[w.value - 1], ], if (p.type == WorkScheduleType.month) 'dates': p.dates, 'dayTime': '${pad(p.dayHour)}:${pad(p.dayMinute)}:00', }, 'startTime': '${dateText(p.startDate)} 00:00:00', 'finishTime': '${dateText(p.endDate)} 23:59:59', }; } }