// +---------------------------------------------------------------------- declare(strict_types=1); namespace addon\mqttbroker\controller\api; use think\facade\Db; use ywxapp\controller\ApiBase; use addon\mqttbroker\service\Store; /** * App 端 MQTT API(需框架会员 JWT 登录) * 路由前缀:/mqttbroker/api/... */ class App extends ApiBase { /** * 当前会员账号(Broker 连接用户名通常为 member.account) */ protected function memberAccount(): string { return (string) ($this->auth->model->account ?? ''); } /** * 我的设备 / 连接列表 * GET /mqttbroker/api/devices */ public function devices() { $account = $this->memberAccount(); if ($account === '') { return $this->result->error('无法获取当前会员账号'); } $list = Db::name('mqttbroker_connection') ->where('username', $account) ->order('update_at', 'desc') ->limit(200) ->field('client_id,username,ip,status,create_at,update_at') ->select() ->toArray(); return $this->result->success(['list' => $list, 'total' => count($list)]); } /** * 我的订阅主题列表 * GET /mqttbroker/api/subscriptions */ public function subscriptions() { $account = $this->memberAccount(); if ($account === '') { return $this->result->error('无法获取当前会员账号'); } $clientIds = Db::name('mqttbroker_connection') ->where('username', $account) ->column('client_id'); $list = []; if ($clientIds) { $list = Db::name('mqttbroker_topic') ->whereIn('client_id', $clientIds) ->order('id', 'desc') ->field('client_id,topic,qos,create_at') ->select() ->toArray(); } return $this->result->success(['list' => $list, 'total' => count($list)]); } /** * 发布消息(服务端代发,强制命名空间隔离) * POST /mqttbroker/api/publish * param: topic, payload, qos(0/1/2), retain(0/1) */ public function publish() { $account = $this->memberAccount(); if ($account === '') { return $this->result->error('无法获取当前会员账号'); } $topic = trim((string) $this->request->post('topic', '')); $payload = (string) $this->request->post('payload', ''); $qos = (int) $this->request->post('qos', 0); $retain = (int) $this->request->post('retain', 0); if ($topic === '' || $topic[0] === '$') { return $this->result->error('主题不能为空,且禁止发布到 $SYS 等系统主题'); } // 命名空间隔离:topic 必须以 {username}/ 开头,避免越权发到他人主题 $prefix = $account . '/'; if (strncmp($topic, $prefix, strlen($prefix)) !== 0) { return $this->result->error("主题必须以 {$prefix} 开头(命名空间隔离)"); } if (!in_array($qos, [0, 1, 2], true)) { $qos = 0; } $ok = Store::enqueueManual($topic, $payload, $qos, $retain, 'app'); if (!$ok) { return $this->result->error('发布失败,请稍后重试'); } return $this->result->success(['topic' => $topic]); } }