// +---------------------------------------------------------------------- declare(strict_types=1); namespace addon\iot\service; use think\facade\Db; /** * 设备日志服务(设备维度留痕:状态上报/事件/命令下发) * * 用途: * - Broker 发布钩子:设备上报 state/event → 写 in 日志 * - 服务端/场景下发命令 → 写 out 日志 * - App/后台按 uuid 分页查询,用于设备诊断与审计 * * 约定: * - 仅在主题属于 /devices/{uuid}/# 时记录,避免海量无关消息入库 * - Broker 长驻进程内调用,全部防御式 try/catch */ class DeviceLogService { /** 确保 device_log 表存在(自愈) */ public static function ensureTable(): bool { $prefix = Db::connect()->getConfig('prefix') ?: 'wxapp_'; try { Db::execute("CREATE TABLE IF NOT EXISTS `{$prefix}iot_device_log` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(64) NOT NULL DEFAULT '' COMMENT '设备uuid', `topic` varchar(255) NOT NULL DEFAULT '' COMMENT '主题', `payload` longtext COMMENT '消息内容', `direction` varchar(8) NOT NULL DEFAULT 'in' COMMENT 'in=设备上报 out=服务端下发', `create_at` int DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_uuid` (`uuid`), KEY `idx_create_at` (`create_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); return true; } catch (\Throwable $e) { return false; } } /** * 写入一条设备日志(自动从主题解析 uuid,非 /devices/... 主题忽略) */ public static function log(string $topic, string $payload, string $direction = 'in'): void { if (!preg_match('#^/devices/([^/]+)/#', $topic, $m)) { return; // 非设备主题不记录 } $uuid = $m[1]; if ($uuid === '' || strlen($uuid) > 64) { return; } self::ensureTable(); try { Db::name('iot_device_log')->insert([ 'uuid' => $uuid, 'topic' => $topic, 'payload' => mb_substr($payload, 0, 65535), 'direction' => $direction === 'out' ? 'out' : 'in', 'create_at' => time(), ]); } catch (\Throwable $e) { // 忽略写失败(不影响 Broker 主流程) } } /** * 分页查询某设备日志(App/后台) * @return array{list:array,total:int} */ public static function page(string $uuid, int $page = 1, int $size = 20, string $direction = ''): array { self::ensureTable(); $q = Db::name('iot_device_log')->where('uuid', $uuid); if ($direction === 'in' || $direction === 'out') { $q->where('direction', $direction); } $total = (int) $q->count(); $list = (clone $q) ->order('id', 'desc') ->page(max(1, $page), min(100, max(1, $size))) ->select() ->toArray(); return ['list' => $list, 'total' => $total]; } /** 删除某设备全部日志(解绑/删除设备时可选调用) */ public static function clear(string $uuid): void { try { Db::name('iot_device_log')->where('uuid', $uuid)->delete(); } catch (\Throwable $e) { } } }