// +---------------------------------------------------------------------- declare(strict_types=1); namespace addon\iot\service; use think\facade\Db; /** * 设备分组服务(智慧生活:房间/自定义分组) * * 权限模型: * - member_id=0 → 后台全局分组(所有会员可见) * - member_id>0 → 仅归属会员可见/可管理 */ class GroupService { /** 确保分组表存在(自愈) */ public static function ensureTables(): bool { $prefix = Db::connect()->getConfig('prefix') ?: 'wxapp_'; try { Db::execute("CREATE TABLE IF NOT EXISTS `{$prefix}iot_group` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `member_id` int unsigned NOT NULL DEFAULT '0', `title` varchar(64) NOT NULL DEFAULT '', `type` varchar(16) NOT NULL DEFAULT 'room', `sort` int NOT NULL DEFAULT '0', `create_at` int DEFAULT NULL, `update_at` int DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_member` (`member_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); Db::execute("CREATE TABLE IF NOT EXISTS `{$prefix}iot_group_device` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `group_id` int unsigned NOT NULL DEFAULT '0', `device_id` int unsigned NOT NULL DEFAULT '0', `create_at` int DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_group_device` (`group_id`,`device_id`), KEY `idx_device` (`device_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); return true; } catch (\Throwable $e) { return false; } } /** 校验分组归属(memberId>0 时仅自己;0 可见全局+自己) */ protected static function checkOwner(int $groupId, int $memberId): void { $g = Db::name('iot_group')->where('id', $groupId)->find(); if (!$g) { throw new \RuntimeException('分组不存在'); } if ($memberId > 0 && (int) $g['member_id'] !== $memberId && (int) $g['member_id'] !== 0) { throw new \RuntimeException('无权操作该分组'); } } /** * 创建/编辑分组 * @return int 分组ID */ public static function save(array $data): int { self::ensureTables(); $id = (int) ($data['id'] ?? 0); $title = mb_substr(trim((string) ($data['title'] ?? '')), 0, 64); if ($title === '') { throw new \InvalidArgumentException('分组名不能为空'); } $now = time(); $row = [ 'member_id' => (int) ($data['member_id'] ?? 0), 'title' => $title, 'type' => ($data['type'] ?? 'room') === 'custom' ? 'custom' : 'room', 'sort' => max(0, (int) ($data['sort'] ?? 0)), 'update_at' => $now, ]; if ($id > 0) { self::checkOwner($id, $row['member_id']); Db::name('iot_group')->where('id', $id)->update($row); } else { $row['create_at'] = $now; $id = (int) Db::name('iot_group')->insertGetId($row); } return $id; } /** 删除分组(连带关联) */ public static function remove(int $groupId, int $memberId = 0): void { self::checkOwner($groupId, $memberId); Db::name('iot_group')->where('id', $groupId)->delete(); Db::name('iot_group_device')->where('group_id', $groupId)->delete(); } /** 分组列表(会员可见:全局 + 自己的),带设备数与在线数 */ public static function groups(int $memberId = 0): array { self::ensureTables(); $q = Db::name('iot_group'); if ($memberId > 0) { $q->where('member_id', 0)->whereOr('member_id', $memberId); } $rows = $q->order('sort', 'asc')->order('id', 'asc')->select()->toArray(); foreach ($rows as &$g) { $deviceIds = Db::name('iot_group_device')->where('group_id', $g['id'])->column('device_id'); $g['device_count'] = count($deviceIds); $g['online_count'] = 0; if ($deviceIds) { $uuids = Db::name('iot_device')->whereIn('id', $deviceIds)->column('uuid'); foreach ($uuids as $u) { if (DeviceService::isOnline($u)) { $g['online_count']++; } } } } return $rows; } /** 设备加入分组(幂等) */ public static function addDevice(int $groupId, int $deviceId, int $memberId = 0): void { self::checkOwner($groupId, $memberId); $exists = Db::name('iot_group_device') ->where('group_id', $groupId)->where('device_id', $deviceId)->count(); if ($exists > 0) { return; } Db::name('iot_group_device')->insert([ 'group_id' => $groupId, 'device_id' => $deviceId, 'create_at' => time(), ]); } /** 设备移出分组 */ public static function removeDevice(int $groupId, int $deviceId, int $memberId = 0): void { self::checkOwner($groupId, $memberId); Db::name('iot_group_device') ->where('group_id', $groupId)->where('device_id', $deviceId)->delete(); } /** 组内设备(含在线/影子增强字段) */ public static function devicesByGroup(int $groupId, int $memberId = 0): array { self::checkOwner($groupId, $memberId); $deviceIds = Db::name('iot_group_device')->where('group_id', $groupId)->column('device_id'); if (!$deviceIds) { return ['list' => [], 'total' => 0]; } $devices = Db::name('iot_device')->whereIn('id', $deviceIds)->select()->toArray(); foreach ($devices as &$d) { $d['online'] = DeviceService::isOnline($d['uuid']); $shadow = DeviceService::shadow($d['uuid']); $d['shadow'] = $shadow ? $shadow['payload'] : ''; } return ['list' => $devices, 'total' => count($devices)]; } /** 设备所在分组(供设备详情展示) */ public static function groupsOfDevice(int $deviceId): array { $gids = Db::name('iot_group_device')->where('device_id', $deviceId)->column('group_id'); if (!$gids) { return []; } return Db::name('iot_group')->whereIn('id', $gids)->column('title'); } /** 解绑/删除设备时清关联 */ public static function clearDevice(int $deviceId): void { Db::name('iot_group_device')->where('device_id', $deviceId)->delete(); } }