feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用

安全(P0)
- 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、
  ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签
  (ywxapp.framework_package_verify,默认开启)
- JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret()
  校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET
- /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验
- 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流
- TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive
- 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php)
- 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis
- paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG

工程化 / 交付
- 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本
- 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过
- 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障
- .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess

结构
- appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
This commit is contained in:
yangwx committed 2026-10-22 00:53:39 +08:00
1 parent 1d49e6f5ee
commit 18101cf11a
636 files changed
+58517 -9519

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YwxApp IoT 物联网平台 · 能力总览</title>
<style>
:root{
--bg:#0b1220; --bg2:#101a2e; --card:#121e33; --line:#1f3050;
--txt:#e7eefc; --sub:#8fa3c8; --acc:#3b82f6; --acc2:#22d3ee;
--ok:#34d399; --warn:#fbbf24;
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--txt);font-family:"PingFang SC","Microsoft YaHei",system-ui,sans-serif;line-height:1.7}
.wrap{max-width:1080px;margin:0 auto;padding:0 24px}
code{font-family:Consolas,Menlo,monospace;font-size:.92em;background:#0a1526;border:1px solid var(--line);border-radius:4px;padding:1px 6px;color:var(--acc2)}
/* ---------- hero ---------- */
.hero{position:relative;padding:80px 0 60px;background:
radial-gradient(ellipse 60% 50% at 70% 10%,rgba(59,130,246,.18),transparent),
radial-gradient(ellipse 40% 40% at 20% 80%,rgba(34,211,238,.10),transparent),
linear-gradient(180deg,#0d1526,var(--bg));border-bottom:1px solid var(--line)}
.badge{display:inline-block;font-size:12px;letter-spacing:2px;color:var(--acc2);border:1px solid var(--line);border-radius:99px;padding:3px 14px;margin-bottom:18px;background:rgba(34,211,238,.06)}
.hero h1{font-size:40px;font-weight:700;letter-spacing:1px}
.hero h1 em{font-style:normal;background:linear-gradient(90deg,var(--acc),var(--acc2));-webkit-background-clip:text;background-clip:text;color:transparent}
.hero p{color:var(--sub);font-size:16px;max-width:640px;margin:14px 0 26px}
.live{display:flex;flex-wrap:wrap;gap:14px}
.live .item{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:10px 18px;min-width:130px}
.live .k{font-size:12px;color:var(--sub)}
.live .v{font-size:22px;font-weight:700;color:var(--acc2);font-family:Consolas,monospace}
.live .v.g{color:var(--ok)}
.refresh{color:var(--sub);font-size:12px;margin-left:auto;align-self:center}
/* ---------- section ---------- */
section{padding:56px 0;border-bottom:1px solid var(--line)}
h2{font-size:24px;margin-bottom:6px}
h2 span{color:var(--acc);margin-right:8px;font-family:Consolas,monospace}
.sub{color:var(--sub);font-size:14px;margin-bottom:28px}
/* 能力卡片 */
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(310px,1fr));gap:16px}
.cap{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:22px;transition:border-color .2s}
.cap:hover{border-color:var(--acc)}
.cap .ico{font-size:24px;width:46px;height:46px;display:flex;align-items:center;justify-content:center;border-radius:10px;background:linear-gradient(135deg,rgba(59,130,246,.2),rgba(34,211,238,.12));margin-bottom:14px}
.cap h3{font-size:16px;margin-bottom:8px}
.cap p{font-size:13px;color:var(--sub)}
.cap p b{color:var(--txt);font-weight:600}
/* 优势行 */
.adv{display:grid;grid-template-columns:repeat(auto-fill,minmax(480px,1fr));gap:14px}
.adv .row{display:flex;gap:14px;background:var(--card);border:1px solid var(--line);border-radius:10px;padding:16px 18px}
.adv .n{flex:none;width:26px;height:26px;border-radius:50%;background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;margin-top:3px}
.adv h4{font-size:14px;margin-bottom:4px}
.adv p{font-size:12.5px;color:var(--sub)}
/* 链路图 */
.flow{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:26px;overflow-x:auto}
.flow .chain{display:flex;align-items:stretch;gap:0;min-width:720px}
.node{flex:1;border:1px solid var(--line);border-radius:10px;padding:14px 10px;text-align:center;background:var(--bg2)}
.node .t{font-size:14px;font-weight:600}
.node .d{font-size:11px;color:var(--sub);margin-top:4px}
.arrow{flex:none;width:56px;display:flex;align-items:center;justify-content:center;color:var(--acc);font-size:18px}
.flow .note{margin-top:16px;font-size:12.5px;color:var(--sub)}
/* 快速开始 */
.steps{counter-reset:s}
.step{position:relative;padding:0 0 22px 44px}
.step::before{counter-increment:s;content:counter(s);position:absolute;left:0;top:0;width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px}
.step h4{font-size:15px;margin-bottom:4px}
.step p{font-size:13px;color:var(--sub)}
footer{padding:34px 0;color:var(--sub);font-size:12px;text-align:center}
footer b{color:var(--txt)}
@media(max-width:720px){.hero h1{font-size:30px}.adv{grid-template-columns:1fr}}
</style>
</head>
<body>
<!-- ============ HERO ============ -->
<div class="hero"><div class="wrap">
<div class="badge">YwxApp Addon · IoT v1.0</div>
<h1>自研 <em>MQTT Broker</em> 物联网平台插件</h1>
<p>设备接入 · 实时监测 · 告警通知 · 场景联动 · 固件 OTA · 共享协作 —— 一套 PHP 常驻进程全搞定,无需 EMQX / Mosquitto 外部中间件。</p>
<div class="live">
<div class="item"><div class="k">Broker 在线连接</div><div class="v g">{$stats.online}</div></div>
<div class="item"><div class="k">接入设备</div><div class="v">{$stats.devices}</div></div>
<div class="item"><div class="k">累计消息</div><div class="v">{$stats.recv}</div></div>
<div class="item"><div class="k">时序数据点</div><div class="v">{$stats.points}</div></div>
<div class="item"><div class="k">运行时长</div><div class="v">{$stats.uptime}</div></div>
<div class="refresh">快照刷新于 {$now}</div>
</div>
</div></div>
<!-- ============ 核心能力 ============ -->
<section><div class="wrap">
<h2><span>01</span>核心能力</h2>
<p class="sub">八大能力域覆盖设备全生命周期,从出厂激活到固件升级。</p>
<div class="grid">
<div class="cap"><div class="ico">🔌</div><h3>自研 MQTT Broker</h3><p>纯 PHP 常驻进程实现 MQTT 3.1.1 协议服务端,QoS0/1/2、保留消息、遗嘱、会话恢复全支持。当前累计转发 <b>{$stats.recv}</b> 条消息,保留消息 <b>{$stats.retained}</b> 条。</p></div>
<div class="cap"><div class="ico">📱</div><h3>设备接入三步走</h3><p>出厂预置激活令牌 → 首次联网一次性换领一机一密凭证 → App 扫码 SN + 配对码绑定认领。激活即换发凭证,杜绝裸设备上线。</p></div>
<div class="cap"><div class="ico">🌡️</div><h3>多路传感器监测</h3><p>每设备 <b>3 路总线 × 3 探头</b>(DS18B20 OneWire),位置级温度/温差上报告警。当前已落库 <b>{$stats.points}</b> 个时序点。</p></div>
<div class="cap"><div class="ico">📈</div><h3>历史曲线双接口</h3><p>原始<b>稀疏点</b>(每条带有效期,App 前向填充)+ <b>分钟级聚合</b>(时间加权平均,step/window 可控:最近 30/60 分钟每分钟一点)。</p></div>
<div class="cap"><div class="ico">🔔</div><h3>云端复核告警</h3><p>设备端位图初判 + 云端 <code>capture()</code> 复核落库,防误报防刷屏(窗口抑制)。未读角标 / 已读回执 / App 推送一路打通。</p></div>
<div class="cap"><div class="ico">⚡</div><h3>场景联动 + 分组</h3><p>温度越限 → 下发命令 → 设备执行,全链路 &lt; 秒级。设备分组批量控制,当前 <b>{$stats.scenes}</b> 个场景在跑。</p></div>
<div class="cap"><div class="ico">🤝</div><h3>共享权限模型</h3><p>主人 / 可控制(perm=2)/ 只读(perm=1)三级权限,HTTP API 与 Broker ACL <b>双层校验</b>。当前有效共享 <b>{$stats.shares}</b> 条。</p></div>
<div class="cap"><div class="ico">🚀</div><h3>固件 OTA 远程升级</h3><p>版本管理 + 分批推送 + 进度跟踪,断电续传由固件侧分片请求保障。无需拆机刷写。</p></div>
</div>
</div></section>
<!-- ============ 架构优势 ============ -->
<section><div class="wrap">
<h2><span>02</span>架构优势</h2>
<p class="sub">不是"又一个 MQTT 接入",每一个设计点都对应一次真实踩坑的教训。</p>
<div class="adv">
<div class="row"><div class="n">1</div><div><h4>零外部中间件依赖</h4><p>Broker 内嵌于插件(<code>php think iot start -d</code>),单机即可跑通,Windows 调试 / Linux 生产同构,运维成本≈0。</p></div></div>
<div class="row"><div class="n">2</div><div><h4>稀疏点存储,带宽与体积双赢</h4><p>温变才上报(10s~120s 一条),每条带 <code>iv</code> 有效期,不做服务端插值放大。500 点曲线 ≈ 一张缩略图的体积。</p></div></div>
<div class="row"><div class="n">3</div><div><h4>配置版本对齐机制</h4><p>服务端 <code>cfg_ver</code> ↔ 设备 <code>sync_ver</code> 回执对齐,retained 下发 + 断线重连自动补推,配置永不漂移、永不丢。</p></div></div>
<div class="row"><div class="n">4</div><div><h4>离线可用的配置中心</h4><p>设备配置真源在云端(后台编辑 → retained 下发 → App 共用同一份),设备离线也能读能存,上线即生效。</p></div></div>
<div class="row"><div class="n">5</div><div><h4>安全三道闸</h4><p>设备侧 uuid/secret 一机一密 · 会员侧 mqttTicket 短时票据 · Broker ACL 按身份收敛订阅/发布范围,越权即断连。</p></div></div>
<div class="row"><div class="n">6</div><div><h4>API 按域分组,一眼可辨</h4><p><code>client/</code> 设备管理 · <code>monitor/</code> 监测 · <code>scene/</code> 场景 · <code>ota/</code> 升级 · <code>mqtt/</code> Broker,旧路径兼容别名平滑迁移。</p></div></div>
</div>
<h2 style="margin-top:36px"><span>03</span>数据链路</h2>
<p class="sub">固件 → Broker → 落库 → App,全程秒级。</p>
<div class="flow">
<div class="chain">
<div class="node"><div class="t">固件 (ESP32-C3)</div><div class="d">3 路 OneWire 总线<br>温变触发上报</div></div>
<div class="arrow">→</div>
<div class="node"><div class="t">MQTT Broker</div><div class="d">鉴权 / ACL / QoS1<br>retained 配置下发</div></div>
<div class="arrow">→</div>
<div class="node"><div class="t">落库 + 告警复核</div><div class="d">iot_sensor_point<br>capture() 防误报</div></div>
<div class="arrow">→</div>
<div class="node"><div class="t">场景引擎</div><div class="d">规则匹配 → 命令<br>outbox 可靠投递</div></div>
<div class="arrow">→</div>
<div class="node"><div class="t">App</div><div class="d">实时推送 / 曲线<br>分钟级聚合查询</div></div>
</div>
<div class="note">下行(配置/命令/OTA)统一走 outbox 队列可靠投递,设备 ack 回执闭环;App 侧「读写一律 HTTP API,MQTT 连接只订不发」。</div>
</div>
</div></section>
<!-- ============ 开放能力 ============ -->
<section><div class="wrap">
<h2><span>04</span>开放能力</h2>
<p class="sub">App 端 HTTP 接口按功能域划分、全部经会员鉴权;接口规模由路由表实时统计,能力扩张页面自动更新。</p>
<div class="grid">
{foreach $apiGroups as $group}
<div class="cap"><div class="ico">{$group.ico}</div><h3>{$group.label}</h3><p><b>{$group.count}</b> 个接口 · {$group.blurb}</p></div>
{/foreach}
</div>
</div></section>
<!-- ============ 消息通道设计 ============ -->
<section><div class="wrap">
<h2><span>05</span>消息通道设计</h2>
<p class="sub">命名空间隔离 + 身份裁决,连接层即安全边界——不依赖主题名保密。</p>
<div class="grid">
<div class="cap"><div class="ico">🔒</div><h3>设备私有命名空间</h3><p>每台设备独占自己的主题空间,发布 / 订阅权限由 Broker ACL 按归属实时裁决,越权订阅直接拒绝。</p></div>
<div class="cap"><div class="ico">📮</div><h3>会员通知通道</h3><p>告警 / 共享 / 系统通知按账号隔离实时推送;App 连接「只订不发」,读写一律走鉴权 HTTP API。</p></div>
<div class="cap"><div class="ico">💾</div><h3>retained 设备影子</h3><p>状态与配置以保留消息存储于 Broker,设备离线可查最后状态,上线即自动对齐配置版本。</p></div>
<div class="cap"><div class="ico">✅</div><h3>回执闭环</h3><p>下行命令 / 配置经队列可靠投递,设备 ack 回执对齐版本号,未达即重发自愈。</p></div>
</div>
</div></section>
<!-- ============ 快速开始 ============ -->
<section><div class="wrap">
<h2><span>06</span>快速开始</h2>
<p class="sub">从零到设备上线,三步。</p>
<div class="steps">
<div class="step"><h4>启动 Broker</h4><p>命令行执行 <code>php think iot start -d</code>,常驻进程开始监听 MQTT 端口并每 2 秒写入运行快照。</p></div>
<div class="step"><h4>登记设备</h4><p>后台「设备管理」预登记 SN 与激活令牌 → 设备首次联网完成入云激活,一次性领取设备凭证。</p></div>
<div class="step"><h4>App 绑定监测</h4><p>App 扫码 SN + 输入配对码绑定 → 配置传感器位置阈值 → 曲线 / 告警 / 场景联动全部就绪。</p></div>
</div>
</div></section>
<footer><div class="wrap">
<b>YwxApp IoT Addon</b> · 自研 MQTT Broker · 当前在线 {$stats.online} 台设备 / {$stats.alerts} 条告警记录 · 数据快照 {$now}
</div></footer>
</body>
</html>