<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title>小氢云日记</title><link>https://bbs.ithxs.com</link><atom:link href="https://bbs.ithxs.com/rss.xml" rel="self" type="application/rss+xml"/><description>小氢云日记小储云教程老韩博客</description><generator>Halo v2.26.1</generator><language>zh-cn</language><image><url>https://bbs.ithxs.com/upload/b_75467a897f57778797ff86339b42300c.jpg</url><title>小氢云日记</title><link>https://bbs.ithxs.com</link></image><lastBuildDate>Wed, 7 Oct 2026 08:15:27 GMT</lastBuildDate><item><title><![CDATA[宝塔 PostgreSQL 报错 permission denied for schema public 怎么解决]]></title><link>https://bbs.ithxs.com/archives/post-baota-pgsql-public-schema-permission</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%AE%9D%E5%A1%94%20PostgreSQL%20%E6%8A%A5%E9%94%99%20permission%20denied%20for%20schema%20public%20%E6%80%8E%E4%B9%88%E8%A7%A3%E5%86%B3&amp;url=/archives/post-baota-pgsql-public-schema-permission" width="1" height="1" alt="" style="opacity:0;">
<h2>现象</h2>
<p>新项目启动时，日志里出现类似错误：</p>
<pre><code>ERROR: permission denied for schema public (SQLSTATE 42501)</code></pre>
<p>同时能看到程序在执行自动建表，例如：</p>
<pre><code>CREATE TABLE "lh_users" (...)</code></pre>
<p>很多人第一反应是：宝塔里不是已经创建了数据库和用户吗？为什么还不行？</p>
<h2>错误是什么意思</h2>
<p>一句话：</p>
<p><strong>数据库已经连上了，但当前业务用户没有在 <code>public</code> 模式下创建表的权限。</strong></p>
<ul>
 <li>能连接：账号密码、库名一般没问题</li>
 <li>建不了表：<code>CREATE TABLE</code> 被 PostgreSQL 拒绝</li>
 <li>错误码 <code>42501</code>：权限不足（insufficient_privilege）</li>
</ul>
<p>这和「表结构写错」通常无关，是权限问题。</p>
<h2>为什么宝塔里创建了还有用？</h2>
<p>宝塔「添加数据库」一般只会：</p>
<ol>
 <li>创建数据库</li>
 <li>创建用户</li>
 <li>给这个用户连库、使用该库的基本权限</li>
</ol>
<p>但从 <strong>PostgreSQL 15</strong> 开始，<code>public</code> 模式默认不再给普通用户 <code>CREATE</code> 权限。</p>
<p>所以会出现这种组合：</p>
<ul>
 <li>宝塔里库、用户都建好了</li>
 <li>程序能连上</li>
 <li>自动迁移建表仍然报 <code>permission denied for schema public</code></li>
</ul>
<p>结论：<strong>在宝塔创建数据库是必要的，但不等于已经有建表权限。</strong></p>
<h2>在宝塔哪里处理</h2>
<p>进入：<strong>软件商店 → PostgreSQL 管理器 → 数据库列表</strong></p>
<p>找到你的业务库（例如库名 <code>login</code>、用户 <code>login</code>），可以先点右侧「权限」看有没有所有者设置。</p>
<p>如果面板里不好改，就用终端补授权（推荐）。</p>
<h2>终端怎么连 PostgreSQL</h2>
<h3>1. 注意：直接敲 <code>psql</code> 常会失败</h3>
<pre><code>sudo -u postgres psql
# sudo: psql: command not found</code></pre>
<p>宝塔安装的 PostgreSQL，客户端通常不在系统 PATH 里，要用完整路径。</p>
<h3>2. 确认 psql 位置</h3>
<pre><code>ls /www/server/pgsql/bin/psql</code></pre>
<p>如果存在，用下面方式连接超级用户：</p>
<pre><code>/www/server/pgsql/bin/psql -U postgres -h 127.0.0.1 -p 5432</code></pre>
<p>密码在 PostgreSQL 管理器页面点「Postgres密码」查看或重置。</p>
<p>如果路径不对，可搜索：</p>
<pre><code>find /www -name psql 2&gt;/dev/null</code></pre>
<h3>3. 给业务用户补权限</h3>
<p>以库名 <code>login</code>、用户 <code>login</code> 为例：</p>
<pre><code>\c login
GRANT USAGE, CREATE ON SCHEMA public TO login;
ALTER DATABASE login OWNER TO login;
\q</code></pre>
<p>把上面的 <code>login</code> 换成你自己的库名和用户名。</p>
<h3>4. 验证是否修好（可选）</h3>
<pre><code>/www/server/pgsql/bin/psql -U login -h 127.0.0.1 -d login -p 5432</code></pre>
<p>进入后执行：</p>
<pre><code>CREATE TABLE test_perm(id int);
DROP TABLE test_perm;
\q</code></pre>
<p>能建能删，说明权限已经够了。然后再启动项目，自动建表一般就能过。</p>
<h2>常见误区</h2>
<figure class="table">
 <table>
  <thead>
   <tr>
    <th>误区</th>
    <th>实际情况</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td>以为是程序代码写错</td>
    <td>多数是 schema 权限不够</td>
   </tr>
   <tr>
    <td>以为宝塔创建库就够了</td>
    <td>PG15+ 还要额外授权</td>
   </tr>
   <tr>
    <td>去改「客户端认证」</td>
    <td>那是连不上时的配置；你现在是能连但不能建表</td>
   </tr>
   <tr>
    <td>直接敲 <code>psql</code></td>
    <td>宝塔环境通常要用 <code>/www/server/pgsql/bin/psql</code></td>
   </tr>
  </tbody>
 </table>
</figure>
<h2>小结</h2>
<p>遇到：</p>
<pre><code>permission denied for schema public (SQLSTATE 42501)</code></pre>
<p>优先检查：</p>
<ol>
 <li>业务用户是不是该库的 owner</li>
 <li>是否已对 <code>public</code> 授予 <code>USAGE, CREATE</code></li>
 <li>授权后重新启动项目</li>
</ol>
<p>按上面做完，新项目自动建表这一步通常就能正常通过。</p>]]></description><guid isPermaLink="false">/archives/post-baota-pgsql-public-schema-permission</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-YWVI.jpg&amp;size=m" type="image/jpeg" length="60277"/><category>教程</category><pubDate>Sun, 4 Oct 2026 17:17:41 GMT</pubDate></item><item><title><![CDATA[彩虹Plus对接小氢云插件介绍：一键对接商品同步与下单回卡]]></title><link>https://bbs.ithxs.com/archives/post-xiaoqingyun-caihong-plugin</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%BD%A9%E8%99%B9Plus%E5%AF%B9%E6%8E%A5%E5%B0%8F%E6%B0%A2%E4%BA%91%E6%8F%92%E4%BB%B6%E4%BB%8B%E7%BB%8D%EF%BC%9A%E4%B8%80%E9%94%AE%E5%AF%B9%E6%8E%A5%E5%95%86%E5%93%81%E5%90%8C%E6%AD%A5%E4%B8%8E%E4%B8%8B%E5%8D%95%E5%9B%9E%E5%8D%A1&amp;url=/archives/post-xiaoqingyun-caihong-plugin" width="1" height="1" alt="" style="opacity:0;">
<p style="">如果你用的是<strong>彩虹自助下单系统（Plus）</strong>，又想对接<strong>小氢云商城</strong>当货源，可以用这款官方风格的扩展插件：名称就叫「小氢云」，装进彩虹的对接扩展目录即可使用。</p>
<blockquote>
 <p style="">一句话：彩虹当前台卖货，小氢云当货源；支持拉分类/商品、余额下单、卡密回传、定时查单同步。</p>
</blockquote>
<h2 style="" id="%E9%80%82%E5%90%88%E8%B0%81">适合谁</h2>
<ul>
 <li>
  <p style="">已经在跑彩虹站，想接小氢云上的货</p>
 </li>
 <li>
  <p style="">需要批量对接、自动下单、卡密自动回填</p>
 </li>
 <li>
  <p style="">不想改彩虹核心文件，只加一个插件目录</p>
 </li>
</ul>
<h2 style="" id="%E6%8F%92%E4%BB%B6%E6%94%BE%E5%93%AA%E9%87%8C">插件放哪里</h2>
<p style="">把整个插件文件夹放到彩虹站的扩展目录：</p>
<pre><code class="language-php">includes/core/extend/Xiaoqingyun/</code></pre>
<p style="">目录内至少包含：</p>
<ul>
 <li>
  <p style=""><code>config.php</code>：插件名称、类型、账号字段说明</p>
 </li>
 <li>
  <p style=""><code>Xiaoqingyun.php</code>：下单、查单、拉商品、价格监控等逻辑</p>
 </li>
</ul>
<p style="">放到位后，后台「对接站点 / 社区」里就能看到「小氢云」选项（无需改 shopbatch 核心代码）。</p>
<h2 style="" id="%E6%80%8E%E4%B9%88%E9%85%8D%E7%BD%AE%E7%A4%BE%E5%8C%BA">怎么配置社区</h2>
<div class="halo-table-wrapper" data-table-layout="auto" style="box-sizing: border-box; overflow-x: auto; overflow-y: hidden; width: 100%; max-width: 100%; min-width: 0;">
 <table data-table-layout="auto" style="display: table; width: 100%; min-width: 100%; table-layout: auto;">
  <tbody>
   <tr>
    <th colspan="1" rowspan="1">
     <p style="">项</p>
    </th>
    <th colspan="1" rowspan="1">
     <p style="">怎么填</p>
    </th>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">社区类型</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">选「小氢云」</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">社区网址</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">小氢云站点域名，例如 <code>https://你的域名/</code></p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">对接密钥</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">小氢云用户后台 → 系统对接 → 复制对接密钥</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">对接 UID</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">选填，仅本地备注</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="">鉴权走小氢云原生同系统对接协议：请求头带 Keys、Timestamp、Sign（签名规则与小氢云文档一致）。</p>
<h2 style="" id="%E8%83%BD%E5%81%9A%E4%BB%80%E4%B9%88">能做什么</h2>
<ol>
 <li>
  <p style=""><strong>拉分类 / 商品</strong>：对接站点里同步分类与商品列表，批量铺货可用。</p>
 </li>
 <li>
  <p style=""><strong>商品详情</strong>：同步名称、价格、库存、最小/最大下单量、下单输入框等。</p>
 </li>
 <li>
  <p style=""><strong>余额下单</strong>：买家在彩虹下单后，插件用对接密钥向小氢云创建订单。</p>
 </li>
 <li>
  <p style=""><strong>卡密回传</strong>：小氢云下单成功若立刻返回卡密，会写回彩虹订单。</p>
 </li>
 <li>
  <p style=""><strong>订单同步</strong>：插件开启定时查单，按商户单号拉取状态与卡密（彩虹扩展插件本身没有异步回调入口，状态主要靠查单同步）。</p>
 </li>
</ol>
<h2 style="" id="%E5%AF%B9%E6%8E%A5%E6%8E%A5%E5%8F%A3%E4%B8%80%E8%A7%88">对接接口一览</h2>
<div class="halo-table-wrapper" data-table-layout="auto" style="box-sizing: border-box; overflow-x: auto; overflow-y: hidden; width: 100%; max-width: 100%; min-width: 0;">
 <table data-table-layout="auto" style="display: table; width: 100%; min-width: 100%; table-layout: auto;">
  <tbody>
   <tr>
    <th colspan="1" rowspan="1">
     <p style="">能力</p>
    </th>
    <th colspan="1" rowspan="1">
     <p style="">小氢云接口</p>
    </th>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">分类</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">/api/getCategoryList</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">商品列表</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">/api/getGoodsList</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">商品详情</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">/api/getGoodsDetail</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">下单</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">/api/createOrder</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">查单</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">/api/getOrderList</p>
    </td>
   </tr>
   <tr>
    <td colspan="1" rowspan="1">
     <p style="">余额</p>
    </td>
    <td colspan="1" rowspan="1">
     <p style="">/api/getBalance</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h2 style="" id="%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F">使用注意</h2>
<ul>
 <li>
  <p style="">密钥填错或站点地址少了协议头，会导致签名失败、下单失败。</p>
 </li>
 <li>
  <p style="">商品要对上小氢云侧的商品 ID（对接商品 ID）。</p>
 </li>
 <li>
  <p style="">库存、最小/最大购买数量以小氢云商品详情为准；若后台某字段仍为空，可先点一次「获取商品信息」再保存。</p>
 </li>
 <li>
  <p style="">异步通知：小氢云侧支持下单传回调地址，但彩虹对接插件框架没有标准回调接收口，当前推荐开启订单同步（cron 查单）。</p>
 </li>
</ul>
<h2 style="" id="%E5%B0%8F%E7%BB%93">小结</h2>
<p style="">「小氢云」彩虹对接插件把两套系统用原生对接协议连起来：装目录、填网址和密钥、同步商品，就能卖小氢云上的货。适合想快速扩货、又不想动彩虹核心的站长。</p>
<p style="">需要安装包或更新日志时，可在博客「小氢云」分类下继续关注后续说明。</p>]]></description><guid isPermaLink="false">/archives/post-xiaoqingyun-caihong-plugin</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-udan.jpg&amp;size=m" type="image/jpeg" length="44980"/><category>小氢云</category><category>插件介绍</category><pubDate>Thu, 1 Oct 2026 10:52:42 GMT</pubDate></item><item><title><![CDATA[AllinSSL 插件教程：CDNFly V6 证书自动部署（含 Docker / Linux 存放与使用）]]></title><link>https://bbs.ithxs.com/archives/post-allinssl-cdnfly-v6-guide</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=AllinSSL%20%E6%8F%92%E4%BB%B6%E6%95%99%E7%A8%8B%EF%BC%9ACDNFly%20V6%20%E8%AF%81%E4%B9%A6%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%EF%BC%88%E5%90%AB%20Docker%20%2F%20Linux%20%E5%AD%98%E6%94%BE%E4%B8%8E%E4%BD%BF%E7%94%A8%EF%BC%89&amp;url=/archives/post-allinssl-cdnfly-v6-guide" width="1" height="1" alt="" style="opacity:0;">
<h1>AllinSSL × CDNFly V6 插件使用教程</h1>
<p>本文说明 <strong>cdnfly-v6</strong> 插件：把 AllinSSL 申请到的证书自动上传到 <strong>CDNFly V6</strong>，并按网站名批量切换 HTTPS 证书。</p>
<p>重点写清两件事：</p>
<ul>
 <li><strong>宝塔 Docker（DK）</strong> 安装时，插件放哪、怎么用</li>
 <li><strong>原生 Linux</strong> 安装时，插件放哪、怎么用</li>
</ul>
<h2>插件下载</h2>
<p><strong>cdnfly-v6</strong> 安装包（Linux amd64）网盘地址：</p>
<p><a href="https://pan.ithxs.com/p/ithxs" target="_blank" rel="noopener noreferrer"><strong>https://pan.ithxs.com/p/ithxs</strong></a></p>
<p>下载后解压，得到可执行文件 <code>cdnfly-v6</code>（或压缩包内的同名二进制），再按下文放到 <code>plugins/cdnfly-v6/</code> 目录并 <code>chmod +x</code>。</p>
<hr>
<h2>一、插件能做什么</h2>
<p>一次部署大致是这样：</p>
<ol>
 <li>用你填写的主控地址 + API Key / Secret 连接 CDNFly</li>
 <li><strong>上传证书一次</strong>（名称带时间戳，避免重名）</li>
 <li>按你填写的<strong>网站名称</strong>（可多个，英文逗号分隔）查找站点</li>
 <li><strong>只改 HTTPS 监听里的证书字段</strong>，保留原有协议、套件、端口等，降低切证失败概率</li>
</ol>
<p>适合已经在 CDNFly V6 里建好网站、只想在证书续期后自动换证的场景。</p>
<hr>
<h2>二、安装方式不同，插件目录也不同</h2>
<p>AllinSSL 有两种常见装法，<strong>插件目录完全不一样</strong>。</p>
<p>很多人进了容器找不到 <code>plugins</code>，或在宿主机改了目录容器里却看不到，就是因为没分清这两种路径。</p>
<h3>1. 宝塔 Docker（DK）安装</h3>
<p>典型容器名类似：<code>ssl-SSL-1</code>。</p>
<table>
 <thead>
  <tr>
   <th>位置</th>
   <th>路径</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>容器内</strong>（程序真正读取）</td>
   <td><code>/www/allinssl/plugins/</code></td>
  </tr>
  <tr>
   <td><strong>宿主机挂载</strong>（宝塔 DK 应用目录，便于上传）</td>
   <td><code>/www/dk_project/dk_app/allinssl/SSL/plugins/</code></td>
  </tr>
 </tbody>
</table>
<p><strong>要点：</strong></p>
<ul>
 <li>把插件放到<strong>宿主机挂载目录</strong>，容器内才能看见。</li>
 <li>只 <code>docker cp</code> 进容器、没有挂载的话，容器重建后会丢。</li>
 <li>建议目录与二进制同名。</li>
</ul>
<p>路径示例：</p>
<pre><code># 宿主机
/www/dk_project/dk_app/allinssl/SSL/plugins/cdnfly-v6/cdnfly-v6

# 容器内对应
/www/allinssl/plugins/cdnfly-v6/cdnfly-v6
</code></pre>
<p>二进制需要是 <strong>Linux amd64</strong>，并赋予可执行权限：</p>
<pre><code>chmod +x /www/dk_project/dk_app/allinssl/SSL/plugins/cdnfly-v6/cdnfly-v6
</code></pre>
<p>若宿主机 <code>plugins</code> 还没挂进容器，请在 Docker 应用里把：</p>
<ul>
 <li>宿主机：<code>SSL/plugins</code></li>
 <li>映射到容器：<code>/www/allinssl/plugins</code></li>
</ul>
<p>然后重启容器。</p>
<p><strong>进容器检查：</strong></p>
<pre><code>docker exec -it -w /www/allinssl ssl-SSL-1 sh
ls -la /www/allinssl/plugins/cdnfly-v6/
</code></pre>
<p><strong>补充：Docker 容器里怎么唤起 AllinSSL 管理命令</strong></p>
<p>容器一般是 Alpine，<strong>没有 bash</strong>。官方 <code>allinssl.sh</code> 写的是 <code>#!/bin/bash</code>，直接执行 <code>./allinssl.sh</code> 常会报 <code>no such file or directory</code>。</p>
<p>请直接调二进制，并带上编号：</p>
<pre><code>cd /www/allinssl
./allinssl 15   # 查看面板地址
./allinssl 3    # 重启服务
./allinssl 10   # 重启 web
./allinssl 13   # 重启后台调度
</code></pre>
<p>注意：无参数执行 <code>./allinssl</code> 会<strong>静默退出</strong>，这是正常现象。</p>
<h3>2. 原生 Linux 安装（非 Docker）</h3>
<p>程序通常在 <code>/www/allinssl/</code>（以你实际安装目录为准）。</p>
<table>
 <thead>
  <tr>
   <th>位置</th>
   <th>路径</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>插件目录</td>
   <td><code>/www/allinssl/plugins/</code></td>
  </tr>
 </tbody>
</table>
<p>放置示例：</p>
<pre><code>/www/allinssl/plugins/cdnfly-v6/cdnfly-v6
</code></pre>
<pre><code>chmod +x /www/allinssl/plugins/cdnfly-v6/cdnfly-v6

# 可用官方脚本菜单，或直接：
cd /www/allinssl &amp;&amp; ./allinssl 3
</code></pre>
<p>原生 Linux 一般有 bash，<code>./allinssl.sh</code> 可用；Docker 里仍优先用 <code>./allinssl &lt;编号&gt;</code>。</p>
<h3>3. 两种方式对照</h3>
<table>
 <thead>
  <tr>
   <th>项目</th>
   <th>宝塔 Docker（DK）</th>
   <th>原生 Linux</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>插件实际目录</td>
   <td>容器内 <code>/www/allinssl/plugins</code></td>
   <td><code>/www/allinssl/plugins</code></td>
  </tr>
  <tr>
   <td>方便上传的位置</td>
   <td>宿主机 <code>.../dk_app/allinssl/SSL/plugins</code>（需挂载）</td>
   <td>直接改服务器上的 plugins</td>
  </tr>
  <tr>
   <td>管理命令</td>
   <td><code>./allinssl &lt;编号&gt;</code>（容器无 bash）</td>
   <td><code>allinssl.sh</code> 或 <code>./allinssl &lt;编号&gt;</code></td>
  </tr>
  <tr>
   <td>换插件后</td>
   <td>通常刷新面板即可；异常时重启调度/服务</td>
   <td>同上</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>三、插件文件怎么放（通用规范）</h2>
<ol>
 <li>从网盘 <a href="https://pan.ithxs.com/p/ithxs" target="_blank" rel="noopener noreferrer">https://pan.ithxs.com/p/ithxs</a> 下载并解压，得到 Linux amd64 二进制 <code>cdnfly-v6</code></li>
 <li>新建目录 <code>plugins/cdnfly-v6/</code></li>
 <li>把二进制放进去，<strong>文件名与目录名一致</strong>，并执行 <code>chmod +x</code></li>
 <li>回到 AllinSSL 面板，工作流部署节点里应能选到 <strong>cdnfly-v6</strong></li>
</ol>
<p>目录结构示例：</p>
<pre><code>plugins/
└── cdnfly-v6/
    ├── cdnfly-v6      # 可执行文件
    └── cdnfly.log     # 跑过之后自动生成的会话日志
</code></pre>
<blockquote>
 <p>若列表没有：检查架构是否 amd64、是否可执行、路径是否在程序读取的 <code>plugins</code> 下（Docker 还要确认挂载）。</p>
</blockquote>
<hr>
<h2>四、在工作流里怎么配</h2>
<h3>1. 授权 / 连接配置</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>base_url</code></td>
   <td>CDNFly 主控地址，如 <code>https://cdn.example.com</code></td>
  </tr>
  <tr>
   <td><code>api_key</code></td>
   <td>API Key</td>
  </tr>
  <tr>
   <td><code>api_secret</code></td>
   <td>API Secret</td>
  </tr>
 </tbody>
</table>
<p>在 CDNFly 控制台创建 API 后填入即可。</p>
<h3>2. 部署动作参数（deploy）</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>必填</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>sites</code></td>
   <td>是</td>
   <td>网站<strong>名称</strong>（与 CDNFly 列表一致），多个用<strong>英文逗号</strong>分隔，如 <code>*.ithxs.com,storage.ithxs.com</code></td>
  </tr>
  <tr>
   <td><code>cert_name</code></td>
   <td>否</td>
   <td>上传到 CDNFly 的证书名；不填则按站点名 + 时间戳自动生成</td>
  </tr>
  <tr>
   <td><code>https_port</code></td>
   <td>否</td>
   <td>强制 HTTPS 端口；一般留空，沿用各站现有端口</td>
  </tr>
 </tbody>
</table>
<p><code>cert</code> / <code>key</code> 由 AllinSSL 工作流在申请成功后自动注入，无需手填。</p>
<h3>3. 重要：填完参数后要切到「自定义」再提交</h3>
<p>这是 <strong>AllinSSL 主程序</strong>的问题，不是插件本身：</p>
<ul>
 <li>默认模式有时会把 <code>params</code> 存成对象</li>
 <li>主程序调用插件前要求 <code>params</code> 是字符串</li>
 <li>结果面板报：<strong>插件参数错误：params</strong>，插件进程都进不去</li>
</ul>
<p><strong>正确操作：</strong> 在部署节点把参数模式切到 <strong>「自定义」</strong>，确认参数以合法形式保存后再提交。</p>
<p>切好之后，工作流日志里应能看到「调用插件 cdnfly-v6:deploy」，而不是卡在 params 校验。</p>
<hr>
<h2>五、执行与结果怎么看</h2>
<h3>1. 面板工作流日志</h3>
<p>成功时大致会有：</p>
<ul>
 <li><code>使用插件部署...</code></li>
 <li><code>调用插件cdnfly-v6:deploy</code></li>
 <li><code>插件响应 ...</code>（主程序会 dump 返回的 status / message / result）</li>
 <li><code>部署成功</code></li>
</ul>
<p>「插件响应」那一行的前缀格式由 AllinSSL 主程序固定，插件只能尽量把 <code>message</code> 写清楚。</p>
<h3>2. 插件自己的详细日志（推荐看这个）</h3>
<p>文件在插件同目录：</p>
<pre><code># Docker 宿主机
/www/dk_project/dk_app/allinssl/SSL/plugins/cdnfly-v6/cdnfly.log

# 容器内 / 原生 Linux
/www/allinssl/plugins/cdnfly-v6/cdnfly.log
</code></pre>
<p>里面是分步会话日志：校验参数 → 上传证书 → 拉网站列表 → 逐站切证 → 汇总。</p>
<p><strong>排错优先看这个文件。</strong></p>
<hr>
<h2>六、常见问题</h2>
<p><strong>Q：Docker 里 <code>./allinssl.sh</code> 没反应 / 报找不到文件？</strong>
 <br>
 A：容器没有 bash。用 <code>./allinssl 15</code> 这类带编号的命令。</p>
<p><strong>Q：宿主机放了插件，面板里没有？</strong>
 <br>
 A：确认 <code>SSL/plugins</code> 已挂载到容器 <code>/www/allinssl/plugins</code>；二进制为 Linux amd64 且可执行。</p>
<p><strong>Q：报「插件参数错误：params」？</strong>
 <br>
 A：部署节点改用「自定义」模式保存参数后再跑。</p>
<p><strong>Q：切证时报 https_listen / 端口无效？</strong>
 <br>
 A：旧逻辑可能整段覆盖监听配置。当前 cdnfly-v6 会合并原 https_listen，只改证书；请用最新二进制。</p>
<p><strong>Q：网站找不到？</strong>
 <br>
 A：<code>sites</code> 填的是 CDNFly 里的<strong>网站名称</strong>，与面板列表一致；多个用英文逗号。</p>
<p><strong>Q：证书名冲突？</strong>
 <br>
 A：默认已加时间戳后缀；也可自己填 <code>cert_name</code>。</p>
<hr>
<h2>七、最短操作清单</h2>
<ol>
 <li>从网盘下载插件：<a href="https://pan.ithxs.com/p/ithxs" target="_blank" rel="noopener noreferrer">https://pan.ithxs.com/p/ithxs</a></li>
 <li>确认自己是 <strong>Docker</strong> 还是 <strong>原生 Linux</strong>，放到对应的 <code>plugins/cdnfly-v6/</code></li>
 <li><code>chmod +x</code> 二进制</li>
 <li>面板工作流选 <strong>cdnfly-v6</strong> → 填主控与 API → 填 <code>sites</code></li>
 <li>参数模式切 <strong>自定义</strong> → 保存</li>
 <li>跑一遍工作流 → 看面板是否部署成功 → 细看 <code>cdnfly.log</code></li>
 <li>到 CDNFly 核对网站 HTTPS 证书是否已换成新上传的那张</li>
</ol>
<p>按上面做完，续期流水线就可以自动把新证推到 CDNFly V6 多个站点。</p>]]></description><guid isPermaLink="false">/archives/post-allinssl-cdnfly-v6-guide</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-lXwQ.jpg&amp;size=m" type="image/jpeg" length="60905"/><category>教程</category><category>插件介绍</category><pubDate>Mon, 28 Sep 2026 06:53:39 GMT</pubDate></item><item><title><![CDATA[小氢云网盘系统部署教程]]></title><link>https://bbs.ithxs.com/archives/01a06945-c28f-700b-88cc-873a57145ddb</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E7%BD%91%E7%9B%98%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B&amp;url=/archives/01a06945-c28f-700b-88cc-873a57145ddb" width="1" height="1" alt="" style="opacity:0;">
<p>小氢云网盘系统支持免费使用。部署前请先到授权站领取授权，再按本文在宝塔面板完成安装与启动。</p>
<p><strong>授权地址：</strong><a href="https://auth-v1.ithxs.com/" target="_blank" rel="noopener noreferrer">https://auth-v1.ithxs.com/</a></p>
<p>注册账号后进入<strong>个人中心</strong>，按页面提示获取本系统对应的授权信息（密钥稍后会在程序启动时填写）。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-zSEn.png&amp;size=m" width="1446px" alt="授权站首页">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-KaDn.png&amp;size=m" width="1365px" alt="注册与登录">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-pHdO.png&amp;size=m" width="958px" alt="个人中心">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-LLxd.png&amp;size=m" width="808px" alt="获取授权">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-sVko.png&amp;size=m" width="2301px" alt="授权密钥示意">
</figure>
<hr>
<h2 id="1-%E5%AE%89%E8%A3%85%E5%AE%9D%E5%A1%94">1. 安装宝塔面板</h2>
<p>推荐使用宝塔面板管理服务器（进程守护、网站反代、数据库与 Redis 都更方便）。若尚未安装，请前往官网安装：</p>
<p><a href="https://www.bt.cn/u/uQtqiK" target="_blank" rel="nofollow noopener noreferrer">宝塔服务器面板 · 一键部署与管理</a></p>
<p>安装完成后，建议先装好：</p>
<ul>
 <li><strong>MySQL</strong></li>
 <li><strong>Redis</strong></li>
 <li><strong>Nginx</strong>（用于域名反代与 HTTPS，按需）</li>
</ul>
<hr>
<h2 id="2-%E4%B8%8A%E4%BC%A0%E7%A8%8B%E5%BA%8F%E6%96%87%E4%BB%B6">2. 上传程序文件</h2>
<p>将安装包上传到服务器合适目录（例如 <code>/www/wwwroot/xqy-storage</code>），解压后可看到主程序二进制 <code>server</code>，以及配置文件等资源。</p>
<p><strong>说明：</strong><code>server</code> 即为网盘系统主程序，后续用宝塔「项目 / 进程守护」来启动它。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-lOpH.png&amp;size=m" width="1435px" alt="上传并解压程序文件">
</figure>
<hr>
<h2 id="3-%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%8E-redis">3. 创建数据库并确认 Redis</h2>
<p>在宝塔中新建一个 MySQL 数据库，并记下：</p>
<ul>
 <li>数据库名</li>
 <li>用户名</li>
 <li>密码</li>
 <li>主机地址（本机一般为 <code>127.0.0.1</code>）与端口（默认 <code>3306</code>）</li>
</ul>
<p>同时确认 Redis 已安装并正常运行，记下 Redis 地址、端口与密码（若设置了密码）。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-TpbY.png&amp;size=m" width="1014px" alt="创建 MySQL 数据库">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-ttXy.png&amp;size=m" width="1137px" alt="安装或确认 Redis">
</figure>
<hr>
<h2 id="4-%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE">4. 修改系统配置</h2>
<p>打开程序目录下的配置文件（常见为 <code>config/config.yaml</code>，若只有模板文件请先复制一份再改），重点核对两块：</p>
<ol>
 <li><strong>数据库配置</strong>：填入上一步创建的 MySQL 信息</li>
 <li><strong>Redis 配置</strong>：填入 Redis 地址、端口、密码与前缀（如有）</li>
</ol>
<p>其余端口、站点相关项可按环境调整。若某项含义不清楚，可将配置片段发给 AI 助手对照说明后再改，避免写错导致启动失败。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-CtiZ.png&amp;size=m" width="1556px" alt="修改数据库与 Redis 配置">
</figure>
<hr>
<h2 id="5-%E5%9C%A8%E5%AE%9D%E5%A1%94%E5%88%9B%E5%BB%BA%E9%A1%B9%E7%9B%AE">5. 在宝塔创建项目（进程守护）</h2>
<p>在宝塔中创建对应项目 / 守护进程，工作目录指向程序解压目录，启动命令指向 <code>server</code> 二进制（或面板提供的 Go / 通用项目启动方式）。</p>
<p>创建后确认：</p>
<ul>
 <li>进程状态为<strong>运行中</strong></li>
 <li>日志无数据库 / Redis 连接错误</li>
 <li>如需外网访问，再为域名配置 Nginx 反向代理到程序监听端口</li>
</ul>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-jRvD.png&amp;size=m" width="1643px" alt="宝塔创建项目">
</figure>
<hr>
<h2 id="6-%E5%A1%AB%E5%86%99%E6%8E%88%E6%9D%83%E5%AE%8C%E6%88%90%E4%B8%8A%E7%BA%BF">6. 填写授权，完成上线</h2>
<p>程序启动后，按页面提示填入在授权站获取的<strong>授权密钥</strong>。填写成功即可正常使用管理后台与网盘功能。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-GFLT.png&amp;size=m" width="1464px" alt="填写授权密钥">
</figure>
<hr>
<h2 id="7-%E5%85%B6%E4%BB%96%E8%AF%B4%E6%98%8E">7. 其他说明</h2>
<h3>日志切割（强烈建议开启）</h3>
<p>长时间运行后，程序日志会持续增大。请在宝塔日志 / 进程相关设置中<strong>开启日志切割</strong>（按天或按大小），避免磁盘被日志占满。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-wbJv.png&amp;size=m" width="1003px" alt="开启日志切割">
</figure>
<h3>常见排查</h3>
<ul>
 <li><strong>启动失败 / 端口占用</strong>：确认旧进程已停止，配置中的端口未被占用</li>
 <li><strong>连不上数据库</strong>：核对库名、账号、密码、主机与端口是否与宝塔一致</li>
 <li><strong>Redis 报错</strong>：确认 Redis 已启动，密码与配置一致</li>
 <li><strong>授权失败</strong>：回到授权站核对产品与密钥是否对应本系统</li>
</ul>
<p>按以上步骤完成后，小氢云网盘即可在服务器上稳定运行。若部署过程中遇到报错，可把日志截屏发到交流群或联系站长协助排查。</p>]]></description><guid isPermaLink="false">/archives/01a06945-c28f-700b-88cc-873a57145ddb</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-CqFI.jpg&amp;size=m" type="image/jpeg" length="103465"/><pubDate>Thu, 3 Sep 2026 22:14:17 GMT</pubDate></item><item><title><![CDATA[小氢云发卡系统介绍]]></title><link>https://bbs.ithxs.com/archives/019f4156-cf04-7458-b76a-62e00536c746</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%8F%91%E5%8D%A1%E7%B3%BB%E7%BB%9F%E4%BB%8B%E7%BB%8D&amp;url=/archives/019f4156-cf04-7458-b76a-62e00536c746" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%8F%91%E5%8D%A1%E7%B3%BB%E7%BB%9F-%C2%B7-%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D"><strong>小氢云发卡系统 · 产品介绍</strong></h1>
<h2 style="" id="%E4%B8%80%E3%80%81%E4%BA%A7%E5%93%81%E5%AE%9A%E4%BD%8D"><strong>一、产品定位</strong></h2>
<p style=""><span><strong>小氢云发卡系统</strong></span>&nbsp;是一套面向数字商品销售场景的&nbsp;<span><strong>企业级自动发卡与多商户商城平台</strong></span>。</p>
<p style="">系统采用&nbsp;<span><strong>Go 后端 + Vue 3 前端</strong></span>&nbsp;前后端分离架构，支持&nbsp;<span><strong>单二进制部署</strong></span>，前端静态资源可嵌入可执行文件，开箱即用、部署简单。适用于游戏点卡、软件激活码、会员账号、虚拟礼品、兑换券、订阅服务等&nbsp;<span><strong>无需物流、支付即交付</strong></span>&nbsp;的数字商品业务。</p>
<p style="">平台同时支持&nbsp;<span><strong>平台自营</strong></span>&nbsp;与&nbsp;<span><strong>多商户入驻</strong></span>&nbsp;两种运营模式：平台方可统一管理全站商品、订单、支付与商户；商户可拥有独立店铺、独立收款、独立品牌展示，在平台规则下自主经营。</p>
<p style=""><span><strong>典型适用场景：</strong></span></p>
<ul>
 <li>
  <p style="">个人或团队售卖游戏点卡、软件授权码</p>
 </li>
 <li>
  <p style="">搭建数字商品自动发卡站</p>
 </li>
 <li>
  <p style="">运营多商户 SaaS 型数字商品交易市场</p>
 </li>
 <li>
  <p style="">需要 PC + 移动端自适应的轻量虚拟商城</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E4%BA%8C%E3%80%81%E6%A0%B8%E5%BF%83%E4%BB%B7%E5%80%BC"><strong>二、核心价值</strong></h2>
<ul>
 <li>
  <p style=""><span><strong>支付成功秒级自动发货</strong></span>，7×24 无人值守，买家下单后数秒内收到卡密或交付内容</p>
 </li>
 <li>
  <p style=""><span><strong>卡密发卡 + URL 交付</strong></span>&nbsp;双模式，覆盖传统卡密与 API 对接场景</p>
 </li>
 <li>
  <p style=""><span><strong>多商户独立店铺</strong></span>，平台统一运维，商户独立经营</p>
 </li>
 <li>
  <p style=""><span><strong>内置在线客服</strong></span>，买家悬浮聊、商户实时回，图文消息、已读状态一应俱全</p>
 </li>
 <li>
  <p style=""><span><strong>经营数据可视化</strong></span>，订单趋势、收入统计、访客地理分布一屏掌握</p>
 </li>
 <li>
  <p style=""><span><strong>多渠道支付</strong></span>，支付宝、微信、QQ 钱包等主流方式灵活启用</p>
 </li>
 <li>
  <p style=""><span><strong>多套前台模板</strong></span>，PC / 移动端 / 通用模板可切换，支持品牌化定制</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E4%B8%89%E3%80%81%E4%B9%B0%E5%AE%B6%E7%AB%AF%EF%BC%88%E7%94%A8%E6%88%B7%E5%89%8D%E5%8F%B0%EF%BC%89%E5%8A%9F%E8%83%BD"><strong>三、买家端（用户前台）功能</strong></h2>
<p style="">用户通过商城前台浏览商品、下单支付、查单取货，全程自动化。</p>
<h3 style="" id="3.1-%E5%95%86%E5%93%81%E6%B5%8F%E8%A7%88%E4%B8%8E%E9%80%89%E8%B4%AD"><strong>3.1 商品浏览与选购</strong></h3>
<ul>
 <li>
  <p style="">按分类浏览商品列表</p>
 </li>
 <li>
  <p style="">商品详情页展示价格、库存、图文介绍、购买须知</p>
 </li>
 <li>
  <p style="">支持商品搜索</p>
 </li>
 <li>
  <p style="">库存显示可配置：精确数量，或「充足 / 较少 / 极少 / 售罄」等友好提示</p>
 </li>
 <li>
  <p style="">首页支持轮播图、站点公告、弹窗公告</p>
 </li>
</ul>
<h3 style="" id="3.2-%E4%B8%8B%E5%8D%95%E4%B8%8E%E6%94%AF%E4%BB%98"><strong>3.2 下单与支付</strong></h3>
<ul>
 <li>
  <p style="">选择购买数量</p>
 </li>
 <li>
  <p style="">填写联系方式（邮箱、QQ 等）</p>
 </li>
 <li>
  <p style="">支持自定义下单字段（如游戏账号、备注信息等，按商品配置）</p>
 </li>
 <li>
  <p style="">多种支付方式可选：支付宝、微信、QQ 钱包等</p>
 </li>
 <li>
  <p style="">支持二维码扫码支付、支付跳转</p>
 </li>
 <li>
  <p style="">支持支付宝订单号验证等免挂收款方式</p>
 </li>
</ul>
<h3 style="" id="3.3-%E8%87%AA%E5%8A%A8%E4%BA%A4%E4%BB%98"><strong>3.3 自动交付</strong></h3>
<p style="">支付成功后，系统按商品配置自动完成交付：</p>
<p style=""><span><strong>卡密发卡模式：</strong></span></p>
<ul>
 <li>
  <p style="">从加密库存中自动出库</p>
 </li>
 <li>
  <p style="">买家通过订单号或联系方式查单，即可查看卡号、卡密</p>
 </li>
 <li>
  <p style="">同时展示商品使用说明</p>
 </li>
</ul>
<p style=""><span><strong>URL 交付模式：</strong></span></p>
<ul>
 <li>
  <p style="">支付成功后自动请求外部 HTTP 接口（GET / POST，同步 / 异步）</p>
 </li>
 <li>
  <p style="">适用于对接外部系统、自动发放账号链接、激活凭证等</p>
 </li>
 <li>
  <p style="">交付过程全程记录日志，便于排查</p>
 </li>
</ul>
<h3 style="" id="3.4-%E8%AE%A2%E5%8D%95%E6%9F%A5%E8%AF%A2"><strong>3.4 订单查询</strong></h3>
<ul>
 <li>
  <p style="">凭订单号或联系方式查询订单状态</p>
 </li>
 <li>
  <p style="">查看支付状态、发货状态、卡密内容</p>
 </li>
 <li>
  <p style="">免费商品可自动完成全流程</p>
 </li>
</ul>
<h3 style="" id="3.5-%E5%9C%A8%E7%BA%BF%E5%AE%A2%E6%9C%8D"><strong>3.5 在线客服</strong></h3>
<ul>
 <li>
  <p style="">前台嵌入悬浮客服挂件</p>
 </li>
 <li>
  <p style="">WebSocket 实时聊天，无需刷新页面</p>
 </li>
 <li>
  <p style="">支持文字与图片消息</p>
 </li>
 <li>
  <p style="">客服在线 / 离线状态可见</p>
 </li>
 <li>
  <p style="">离线时可展示提示语，并支持邮件通知客服</p>
 </li>
</ul>
<h3 style="" id="3.6-%E5%A4%9A%E5%95%86%E6%88%B7%E5%BA%97%E9%93%BA%E8%AE%BF%E9%97%AE"><strong>3.6 多商户店铺访问</strong></h3>
<ul>
 <li>
  <p style="">每位商户拥有独立店铺访问地址（专属路由）</p>
 </li>
 <li>
  <p style="">店铺可单独配置模板、Logo、公告、支付方式</p>
 </li>
 <li>
  <p style="">买家在商户店铺内购物，商品、订单、支付、客服均与对应商户隔离</p>
 </li>
</ul>
<h3 style="" id="3.7-%E5%89%8D%E5%8F%B0%E6%A8%A1%E6%9D%BF%E4%BD%93%E7%B3%BB"><strong>3.7 前台模板体系</strong></h3>
<p style="">系统提供多套开箱即用的前台模板，可按需切换：</p>
<ul>
 <li>
  <p style=""><span><strong>PC 端模板</strong></span>：适合桌面浏览器</p>
 </li>
 <li>
  <p style=""><span><strong>移动端模板</strong></span>：适合手机访问，沉浸式设计</p>
 </li>
 <li>
  <p style=""><span><strong>货源风单页模板</strong></span>：简洁高效</p>
 </li>
 <li>
  <p style=""><span><strong>精品发卡模板</strong></span>：独立布局风格</p>
 </li>
 <li>
  <p style=""><span><strong>uniapp 风格模板</strong></span>：跨端体验</p>
 </li>
 <li>
  <p style=""><span><strong>现代化 UI 模板</strong></span>：视觉更现代</p>
 </li>
</ul>
<p style="">系统会根据访问设备自动选择 PC 或移动模板，也支持手动指定。</p>
<hr>
<h2 style="" id="%E5%9B%9B%E3%80%81%E5%B9%B3%E5%8F%B0%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%E5%8A%9F%E8%83%BD"><strong>四、平台管理后台功能</strong></h2>
<p style="">平台管理员通过 Web 管理后台，掌控全站运营。</p>
<h3 style="" id="4.1-%E6%95%B0%E6%8D%AE%E4%BB%AA%E8%A1%A8%E7%9B%98"><strong>4.1 数据仪表盘</strong></h3>
<ul>
 <li>
  <p style="">总销售额、今日销售额、成本、利润</p>
 </li>
 <li>
  <p style="">订单总数、今日订单、待处理订单</p>
 </li>
 <li>
  <p style="">在售商品数、低库存预警</p>
 </li>
 <li>
  <p style="">销售趋势图表（按日 / 按周）</p>
 </li>
 <li>
  <p style="">支付方式分布（成交额占比）</p>
 </li>
 <li>
  <p style="">热销商品排行榜</p>
 </li>
 <li>
  <p style="">访问地理分布大屏（全球视图 / 全国视图）</p>
 </li>
 <li>
  <p style="">官方公告与广告位展示</p>
 </li>
</ul>
<h3 style="" id="4.2-%E5%95%86%E5%93%81%E4%B8%8E%E5%88%86%E7%B1%BB%E7%AE%A1%E7%90%86"><strong>4.2 商品与分类管理</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>分类管理</strong></span>：增删改查、排序、启用 / 禁用</p>
 </li>
 <li>
  <p style=""><span><strong>商品管理</strong></span>：</p>
  <ul>
   <li>
    <p style="">价格、成本、库存、排序</p>
   </li>
   <li>
    <p style="">富文本商品详情、使用说明</p>
   </li>
   <li>
    <p style="">自定义下单字段配置</p>
   </li>
   <li>
    <p style="">交付方式选择（卡密 / URL）</p>
   </li>
   <li>
    <p style="">URL 交付规则配置（请求地址、方法、参数、成功判断条件）</p>
   </li>
   <li>
    <p style="">商品启用 / 禁用、上下架</p>
   </li>
  </ul>
 </li>
</ul>
<h3 style="" id="4.3-%E8%AE%A2%E5%8D%95%E7%AE%A1%E7%90%86"><strong>4.3 订单管理</strong></h3>
<ul>
 <li>
  <p style="">订单列表：按状态、时间、商品等筛选</p>
 </li>
 <li>
  <p style="">订单详情：买家信息、支付信息、交付内容</p>
 </li>
 <li>
  <p style="">手动标记支付、手动补发卡密</p>
 </li>
 <li>
  <p style="">取消订单、批量删除</p>
 </li>
 <li>
  <p style="">订单流程日志：状态变更全链路追踪（待支付 → 已支付 → 已发卡 → 已取消）</p>
 </li>
</ul>
<h3 style="" id="4.4-%E5%8D%A1%E5%AF%86%E7%AE%A1%E7%90%86"><strong>4.4 卡密管理</strong></h3>
<ul>
 <li>
  <p style="">卡号、卡密&nbsp;<span><strong>AES-256 加密存储</strong></span>，安全可靠</p>
 </li>
 <li>
  <p style="">单条录入、批量导入、批量删除</p>
 </li>
 <li>
  <p style="">与商品库存联动，发卡后自动扣减</p>
 </li>
 <li>
  <p style="">卡密绑定订单，防止重复发放</p>
 </li>
</ul>
<h3 style="" id="4.5-%E6%94%AF%E4%BB%98%E9%85%8D%E7%BD%AE"><strong>4.5 支付配置</strong></h3>
<p style="">支持多种支付通道类型：</p>
<ul>
 <li>
  <p style=""><span><strong>易支付 V1 / V2</strong></span>：聚合支付对接</p>
 </li>
 <li>
  <p style=""><span><strong>支付宝备注转账</strong></span>：好友转账 / 备注收款</p>
 </li>
 <li>
  <p style=""><span><strong>支付宝订单号验证</strong></span>：免挂收款验证</p>
 </li>
</ul>
<p style="">各通道可独立启用支付宝、微信、QQ 支付开关，满足多样化收款需求。支付异步回调与同步跳转分离，确保订单状态准确。</p>
<h3 style="" id="4.6-%E5%A4%9A%E5%95%86%E6%88%B7%E7%AE%A1%E7%90%86%EF%BC%88%E5%B9%B3%E5%8F%B0%E6%8B%93%E5%B1%95%E8%83%BD%E5%8A%9B%EF%BC%89"><strong>4.6 多商户管理（平台拓展能力）</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>商户列表</strong></span>：开通 / 禁用、到期时间管理、余额调整、密码重置</p>
 </li>
 <li>
  <p style=""><span><strong>商户详情</strong></span>：商品、分类、订单、操作日志一站式查看</p>
 </li>
 <li>
  <p style=""><span><strong>商品审核</strong></span>：可配置商户商品是否需平台审核后上架</p>
 </li>
 <li>
  <p style=""><span><strong>套餐管理</strong></span>：月租 / 季租 / 年租 / 永久等套餐 CRUD</p>
 </li>
 <li>
  <p style=""><span><strong>商户公告</strong></span>：面向商户的官方通知发布</p>
 </li>
 <li>
  <p style=""><span><strong>多商户经营仪表盘</strong></span>：商户维度数据概览</p>
 </li>
</ul>
<h3 style="" id="4.7-%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE"><strong>4.7 系统配置</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>网站配置</strong></span>：站点标题、名称、SEO 关键词与描述、Logo、Favicon、首页公告、弹窗公告、轮播图</p>
 </li>
 <li>
  <p style=""><span><strong>订单号规则</strong></span>：前缀、时间戳、日期随机、日期序号、纯随机等多种生成方式</p>
 </li>
 <li>
  <p style=""><span><strong>模板管理</strong></span>：PC / 移动 / 通用模板在线切换，支持模板可配置项</p>
 </li>
 <li>
  <p style=""><span><strong>域名管理</strong></span>：多域名绑定、启用 / 禁用、访问控制</p>
 </li>
 <li>
  <p style=""><span><strong>邮箱配置</strong></span>：多 SMTP 账号、连接测试、发信测试</p>
 </li>
 <li>
  <p style=""><span><strong>节点管理</strong></span>：云端授权节点切换、连接测试</p>
 </li>
 <li>
  <p style=""><span><strong>验证码配置</strong></span>：数字 / 字母 / 极验人机验证</p>
 </li>
</ul>
<h3 style="" id="4.8-%E5%9C%A8%E7%BA%BF%E5%AE%A2%E6%9C%8D%E5%B7%A5%E4%BD%9C%E5%8F%B0"><strong>4.8 在线客服工作台</strong></h3>
<ul>
 <li>
  <p style="">WebSocket 实时会话列表</p>
 </li>
 <li>
  <p style="">与买家实时文字、图片聊天</p>
 </li>
 <li>
  <p style="">消息已读标记、会话关闭</p>
 </li>
 <li>
  <p style="">客服图片管理与过期清理</p>
 </li>
</ul>
<h3 style="" id="4.9-%E6%97%A5%E5%BF%97%E4%B8%AD%E5%BF%83"><strong>4.9 日志中心</strong></h3>
<ul>
 <li>
  <p style="">管理员登录日志</p>
 </li>
 <li>
  <p style="">访问日志（IP、路径、地理位置、耗时）</p>
 </li>
 <li>
  <p style="">操作日志（管理员行为记录）</p>
 </li>
 <li>
  <p style="">邮件发送日志</p>
 </li>
 <li>
  <p style="">URL 交付请求日志</p>
 </li>
 <li>
  <p style="">产品版本更新日志</p>
 </li>
</ul>
<h3 style="" id="4.10-%E7%B3%BB%E7%BB%9F%E5%B7%A5%E5%85%B7"><strong>4.10 系统工具</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>在线升级</strong></span>：版本检测、程序热更新、服务重启</p>
 </li>
 <li>
  <p style=""><span><strong>接口文档</strong></span>：内置前台公共 API 文档在线查看</p>
 </li>
 <li>
  <p style=""><span><strong>服务器监控</strong></span>：WebSocket 实时查看 CPU、内存、磁盘等资源</p>
 </li>
 <li>
  <p style=""><span><strong>演示站模式</strong></span>：定时重置业务数据，适合演示环境</p>
 </li>
 <li>
  <p style=""><span><strong>守护进程</strong></span>：程序意外退出自动重启</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E4%BA%94%E3%80%81%E5%95%86%E6%88%B7%E5%90%8E%E5%8F%B0%E5%8A%9F%E8%83%BD"><strong>五、商户后台功能</strong></h2>
<p style="">每位入驻商户拥有独立后台，完成从开店到收款的完整经营闭环。</p>
<h3 style="" id="5.1-%E7%BB%8F%E8%90%A5%E6%80%BB%E8%A7%88%EF%BC%88%E4%BB%AA%E8%A1%A8%E7%9B%98%EF%BC%89"><strong>5.1 经营总览（仪表盘）</strong></h3>
<ul>
 <li>
  <p style="">账户余额、今日交易额、今日利润、今日订单数</p>
 </li>
 <li>
  <p style="">在售商品数、库存不足提醒、待处理订单</p>
 </li>
 <li>
  <p style="">账户到期状态提醒（已到期 / 即将到期 / 正常）</p>
 </li>
 <li>
  <p style="">销售趋势分析（近 7 天 / 近 30 天）</p>
 </li>
 <li>
  <p style="">支付方式分布、订单状态分布</p>
 </li>
 <li>
  <p style="">热销商品排行榜（按成交额）</p>
 </li>
</ul>
<h3 style="" id="5.2-%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90"><strong>5.2 访问数据分析</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>地理分布大屏</strong></span>：全球 / 全国视图一键切换</p>
 </li>
 <li>
  <p style="">时间范围：24 小时 / 7 天 / 30 天 / 全部</p>
 </li>
 <li>
  <p style="">总访问量、国家数、省份数统计</p>
 </li>
 <li>
  <p style="">国家 / 省份访问排行榜</p>
 </li>
 <li>
  <p style=""><span><strong>访问日志</strong></span>：按 IP、路径、时间筛选，查看详细访问记录</p>
 </li>
 <li>
  <p style=""><span><strong>WebSocket 实时推送</strong></span>：访问事件、统计数据实时更新</p>
 </li>
</ul>
<h3 style="" id="5.3-%E5%BA%97%E9%93%BA%E7%AE%A1%E7%90%86"><strong>5.3 店铺管理</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>独立店铺路由</strong></span>：绑定 / 更换专属访问地址</p>
 </li>
 <li>
  <p style=""><span><strong>模板选择</strong></span>：通用 / PC / 移动端模板</p>
 </li>
 <li>
  <p style=""><span><strong>品牌与 SEO</strong></span>：站点标题、关键词、描述、Logo、Favicon</p>
 </li>
 <li>
  <p style=""><span><strong>公告管理</strong></span>：站点公告、弹窗公告（富文本）</p>
 </li>
 <li>
  <p style=""><span><strong>在线客服配置</strong></span>：开关、欢迎语、离线提示、离线邮件通知</p>
 </li>
</ul>
<h3 style="" id="5.4-%E5%95%86%E5%93%81%E4%B8%8E%E8%AE%A2%E5%8D%95"><strong>5.4 商品与订单</strong></h3>
<ul>
 <li>
  <p style="">商品创建、编辑、删除、排序</p>
 </li>
 <li>
  <p style="">平台开启审核时，新建 / 变更商品进入待审状态</p>
 </li>
 <li>
  <p style="">自建商品分类体系</p>
 </li>
 <li>
  <p style="">卡密库存：单条录入、批量导入、批量删除</p>
 </li>
 <li>
  <p style="">订单列表与详情查询</p>
 </li>
 <li>
  <p style="">URL 交付 API 调用日志</p>
 </li>
 <li>
  <p style="">操作记录留痕</p>
 </li>
</ul>
<h3 style="" id="5.5-%E6%94%AF%E4%BB%98%E4%B8%8E%E8%B4%A2%E5%8A%A1"><strong>5.5 支付与财务</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>收款配置</strong></span>：自有支付通道增删改、启用 / 停用</p>
 </li>
 <li>
  <p style=""><span><strong>余额充值</strong></span>：发起充值、查看充值记录、在线支付</p>
 </li>
 <li>
  <p style=""><span><strong>套餐订阅</strong></span>：查看 / 购买套餐、自动续费开关、订阅记录</p>
 </li>
 <li>
  <p style="">到期邮件提醒（7 / 3 / 1 天可配置）</p>
 </li>
</ul>
<h3 style="" id="5.6-%E5%85%AC%E5%91%8A%E4%B8%AD%E5%BF%83"><strong>5.6 公告中心</strong></h3>
<ul>
 <li>
  <p style="">查看平台发布的官方公告</p>
 </li>
 <li>
  <p style="">公告详情阅读、阅读次数统计</p>
 </li>
</ul>
<h3 style="" id="5.7-%E5%AE%A2%E6%9C%8D%E5%B7%A5%E4%BD%9C%E5%8F%B0"><strong>5.7 客服工作台</strong></h3>
<ul>
 <li>
  <p style="">与买家实时聊天（文字 + 图片）</p>
 </li>
 <li>
  <p style="">会话管理、在线状态设置</p>
 </li>
</ul>
<h3 style="" id="5.8-%E8%B4%A6%E5%8F%B7%E4%B8%8E%E5%AE%89%E5%85%A8"><strong>5.8 账号与安全</strong></h3>
<ul>
 <li>
  <p style="">个人资料维护、修改密码</p>
 </li>
 <li>
  <p style="">邮箱验证码绑定 / 解绑</p>
 </li>
 <li>
  <p style="">聚合登录：QQ / 微信 / 支付宝 OAuth 绑定与快捷登录</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E5%85%AD%E3%80%81%E6%94%AF%E4%BB%98%E4%B8%8E%E8%AE%A2%E5%8D%95%E8%83%BD%E5%8A%9B%E8%AF%A6%E8%A7%A3"><strong>六、支付与订单能力详解</strong></h2>
<h3 style="" id="6.1-%E8%AE%A2%E5%8D%95%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><strong>6.1 订单生命周期</strong></h3>
<p style=""><span>买家下单 → 待支付 → 支付成功 → 自动发货 / 手动补发 → 已交付</span></p>
<p style=""><span> ↓</span></p>
<p style=""><span> 取消订单</span></p>
<ul>
 <li>
  <p style="">支持免费商品自动完成</p>
 </li>
 <li>
  <p style="">支付异步回调 + 同步跳转双保险</p>
 </li>
 <li>
  <p style="">下单成功、发卡成功均可触发邮件通知</p>
 </li>
</ul>
<h3 style="" id="6.2-%E6%94%AF%E4%BB%98%E9%80%9A%E9%81%93"><strong>6.2 支付通道</strong></h3>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 200px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <th colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; font-weight: 600; text-align: left; vertical-align: top; white-space: nowrap; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style=""><strong>通道类型</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; font-weight: 600; text-align: left; vertical-align: top; white-space: nowrap; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style=""><strong>说明</strong></p>
    </th>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">易支付 V1</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">经典聚合支付对接</p>
    </td>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">易支付 V2</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">新版聚合支付协议</p>
    </td>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">支付宝备注转账</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">好友转账 / 备注收款</p>
    </td>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">支付宝订单号验证</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">免挂、买家提交订单号验证</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="">平台与商户各自维护支付通道，互不干扰。</p>
<h3 style="" id="6.3-%E5%8D%A1%E5%AF%86%E5%AE%89%E5%85%A8"><strong>6.3 卡密安全</strong></h3>
<ul>
 <li>
  <p style="">卡号、卡密 AES-256 加密存储</p>
 </li>
 <li>
  <p style="">密钥基于系统 JWT 密钥派生，即使数据库泄露也难以解密</p>
 </li>
 <li>
  <p style="">发卡后绑定订单，防止重复出库</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E4%B8%83%E3%80%81%E5%9C%A8%E7%BA%BF%E5%AE%A2%E6%9C%8D%E4%BD%93%E7%B3%BB"><strong>七、在线客服体系</strong></h2>
<p style="">三端 WebSocket 实时通信，售前售后沟通顺畅：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 200px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <th colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; font-weight: 600; text-align: left; vertical-align: top; white-space: nowrap; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style=""><strong>角色</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; font-weight: 600; text-align: left; vertical-align: top; white-space: nowrap; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style=""><strong>能力</strong></p>
    </th>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">买家</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">前台悬浮挂件发起会话、发送文字 / 图片</p>
    </td>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">商户</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">商户后台客服工作台实时回复</p>
    </td>
   </tr>
   <tr style="-electron-corner-smoothing: 60%; border-width: 0px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 1px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">平台</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="-electron-corner-smoothing: 60%; text-align: left; vertical-align: top; border-bottom: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); border-right: 0px solid color(srgb 0.0784314 0.0784314 0.0784314 / 0.08); padding: 5px 9px; scrollbar-color: color(srgb 0.0784314 0.0784314 0.0784314 / 0.08) rgba(0, 0, 0, 0); scrollbar-width: thin; box-sizing: border-box;">
     <p style="">管理员客服工作台统一管理</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<ul>
 <li>
  <p style="">欢迎语、离线提示可配置</p>
 </li>
 <li>
  <p style="">客服离线时自动邮件通知</p>
 </li>
 <li>
  <p style="">图片消息带过期时间，定时清理节省空间</p>
 </li>
 <li>
  <p style="">消息已读状态可见</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E5%85%AB%E3%80%81%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF"><strong>八、适用场景</strong></h2>
<h3 style="" id="%E5%9C%BA%E6%99%AF%E4%B8%80%EF%BC%9A%E6%B8%B8%E6%88%8F%E7%82%B9%E5%8D%A1-%26-%E6%BF%80%E6%B4%BB%E7%A0%81%EF%BC%88%E9%AB%98%E9%A2%91%E5%9C%BA%E6%99%AF%EF%BC%89"><strong>场景一：游戏点卡 &amp; 激活码（高频场景）</strong></h3>
<p style="">批量导入卡密、自动出库、库存预警，支持多规格 SKU，支付成功秒级自动发货，7×24 无人值守。</p>
<h3 style="" id="%E5%9C%BA%E6%99%AF%E4%BA%8C%EF%BC%9A%E4%BC%9A%E5%91%98-%26-%E8%AE%A2%E9%98%85%E6%9C%8D%E5%8A%A1"><strong>场景二：会员 &amp; 订阅服务</strong></h3>
<p style="">对接 URL 交付或 API 回调，支付成功后自动发放账号、链接或凭证，减少人工客服成本。</p>
<h3 style="" id="%E5%9C%BA%E6%99%AF%E4%B8%89%EF%BC%9A%E8%99%9A%E6%8B%9F%E7%A4%BC%E5%93%81-%26-%E5%85%91%E6%8D%A2%E5%88%B8"><strong>场景三：虚拟礼品 &amp; 兑换券</strong></h3>
<p style="">支持限时链接、一次性卡密，适合活动促销、渠道分销，订单与交付全程可追溯。</p>
<h3 style="" id="%E5%9C%BA%E6%99%AF%E5%9B%9B%EF%BC%9A%E5%A4%9A%E5%95%86%E6%88%B7-saas-%E5%B9%B3%E5%8F%B0"><strong>场景四：多商户 SaaS 平台</strong></h3>
<p style="">为平台方提供商户入驻、独立店铺、统一支付与运维能力，快速搭建数字商品交易市场。</p>
<hr>
<h2 style="" id="%E4%B9%9D%E3%80%81%E6%8E%A5%E5%85%A5%E6%B5%81%E7%A8%8B%EF%BC%88%E4%B8%89%E6%AD%A5%E4%B8%8A%E7%BA%BF%EF%BC%89"><strong>九、接入流程（三步上线）</strong></h2>
<ol>
 <li>
  <p style=""><span><strong>注册并配置店铺</strong></span>&nbsp;— 填写基本信息，设置店铺名称、支付渠道与商品分类</p>
 </li>
 <li>
  <p style=""><span><strong>上架商品与库存</strong></span>&nbsp;— 导入卡密或配置 URL 交付规则，设置价格、库存与购买须知</p>
 </li>
 <li>
  <p style=""><span><strong>分享链接自动成交</strong></span>&nbsp;— 买家下单支付后系统自动发货，订单与交付记录后台实时可查</p>
 </li>
</ol>
<hr>
<h2 style="" id="%E5%8D%81%E3%80%81%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BA%AE%E7%82%B9"><strong>十、技术架构与亮点</strong></h2>
<h3 style="" id="10.1-%E9%83%A8%E7%BD%B2%E5%BD%A2%E6%80%81"><strong>10.1 部署形态</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>单二进制部署</strong></span>：Go 后端 + 前端静态资源 embed，一个文件即可运行</p>
 </li>
 <li>
  <p style="">也支持将前台模板放到外部目录，热切换无需重新编译</p>
 </li>
 <li>
  <p style="">守护进程：崩溃自动重启，保障服务可用性</p>
 </li>
</ul>
<h3 style="" id="10.2-%E6%8A%80%E6%9C%AF%E6%A0%88"><strong>10.2 技术栈</strong></h3>
<ul>
 <li>
  <p style=""><span><strong>后端</strong></span>：Go + Gin + MySQL + Redis + JWT + WebSocket</p>
 </li>
 <li>
  <p style=""><span><strong>平台管理后台</strong></span>：Vue 3 + Vben Admin 5 + Ant Design Vue</p>
 </li>
 <li>
  <p style=""><span><strong>商户后台</strong></span>：Vue 3 + Vben Admin 5（独立应用 + 平台内嵌双入口）</p>
 </li>
 <li>
  <p style=""><span><strong>产品官网</strong></span>：Vue 3 + Vben Admin 5</p>
 </li>
 <li>
  <p style=""><span><strong>用户前台</strong></span>：Vue 3 + Vant / Ant Design Vue / Tailwind / wot-ui 等多套模板</p>
 </li>
</ul>
<h3 style="" id="10.3-%E6%80%A7%E8%83%BD%E4%B8%8E%E5%AE%89%E5%85%A8"><strong>10.3 性能与安全</strong></h3>
<ul>
 <li>
  <p style="">Redis 缓存：站点配置、商品、分类、商户店铺等热点数据</p>
 </li>
 <li>
  <p style="">限流中间件、CORS、请求 ID 追踪</p>
 </li>
 <li>
  <p style="">JWT 认证（管理员 / 商户分离）</p>
 </li>
 <li>
  <p style="">卡密 AES 加密、密码加盐哈希</p>
 </li>
 <li>
  <p style="">演示站写保护</p>
 </li>
 <li>
  <p style="">IP 地理位置解析，支撑访问统计大屏</p>
 </li>
</ul>
<h3 style="" id="10.4-%E8%BF%90%E7%BB%B4%E8%83%BD%E5%8A%9B"><strong>10.4 运维能力</strong></h3>
<ul>
 <li>
  <p style="">在线升级：检测新版本、上传更新包、热重启</p>
 </li>
 <li>
  <p style="">自动建表 / 补字段：重启即完成数据库迁移</p>
 </li>
 <li>
  <p style="">Gzip 压缩、静态资源浏览器缓存</p>
 </li>
 <li>
  <p style="">内置 API 文档</p>
 </li>
</ul>
<hr>
<h2 style="" id="%E5%8D%81%E4%B8%80%E3%80%81%E4%BA%A7%E5%93%81%E5%AE%98%E7%BD%91"><strong>十一、产品官网</strong></h2>
<p style="">系统内置独立产品官网模块，包含：</p>
<ul>
 <li>
  <p style="">产品介绍首页（Hero、数据统计、支付通道、功能卡片、使用场景、接入流程、客户反馈、FAQ）</p>
 </li>
 <li>
  <p style="">商户注册页（邮箱验证码注册、人机验证）</p>
 </li>
 <li>
  <p style="">支持自定义访问前缀，可独立域名部署</p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019f4156-cf04-7458-b76a-62e00536c746</guid><dc:creator>Aツ韩先生</dc:creator><category>小氢云</category><category>产品介绍</category><category>产品介绍</category><pubDate>Wed, 8 Jul 2026 10:49:48 GMT</pubDate></item><item><title><![CDATA[需求大厅插件商店密钥填写教程]]></title><link>https://bbs.ithxs.com/archives/019f1d3d-4a7a-7571-91c6-ef968891ae62</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E9%9C%80%E6%B1%82%E5%A4%A7%E5%8E%85%E6%8F%92%E4%BB%B6%E5%95%86%E5%BA%97%E5%AF%86%E9%92%A5%E5%A1%AB%E5%86%99%E6%95%99%E7%A8%8B&amp;url=/archives/019f1d3d-4a7a-7571-91c6-ef968891ae62" width="1" height="1" alt="" style="opacity:0;">
<p style=""><a href="https://auth-v1.ithxs.com/#/backend/profile" target="_blank" rel="">https://auth-v1.ithxs.com/#/backend/profile</a></p>
<p style="">先生成RSA签名</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-QmRB.png&amp;size=m" width="2314px">
</figure>
<p style=""></p>
<p style="">在你的商城后台设置里有这两个配置项，把刚才生成的填进去就行了</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-Dwnj.png&amp;size=m" width="1323px">
</figure>
<p style=""></p>
<p style="">前提是，生成这个密钥的这个平台用户要和你的授权关联，就是说授权必须是这个用户关联的才可以！</p>]]></description><guid isPermaLink="false">/archives/019f1d3d-4a7a-7571-91c6-ef968891ae62</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-vAre.jpg&amp;size=m" type="image/jpeg" length="112188"/><pubDate>Wed, 1 Jul 2026 10:38:04 GMT</pubDate></item><item><title><![CDATA[小氢云商城系统 — 产品介绍]]></title><link>https://bbs.ithxs.com/archives/post-c46df9ea1422</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%95%86%E5%9F%8E%E7%B3%BB%E7%BB%9F%20%E2%80%94%20%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D&amp;url=/archives/post-c46df9ea1422" width="1" height="1" alt="" style="opacity:0;">
<h1>小氢云商城系统 — 产品介绍</h1>
<hr>
<h2>一句话定位</h2>
<p><strong>小氢云商城系统</strong>是一款面向<strong>虚拟商品 / 数字商品</strong>经营者的全链路 SaaS 电商平台：以<strong>自动发卡、货源对接、订单自动化</strong>为核心，覆盖 PC 商城、H5 移动端、用户中心、管理后台，并集成虚拟主机、分站分销、第三方电商对接与 AI 运营能力。</p>
<blockquote>
 <p>让虚拟商品生意：<strong>上架快、发货快、对接快、运营快。</strong></p>
</blockquote>
<hr>
<h2>一、产品定位</h2>
<p>小氢云商城系统是一款面向数字商品（虚拟商品）商家的全功能自动化电商平台。系统以<strong>自动发卡</strong>为核心，上游对接多家货源供应商实现订单自动处理、卡密自动返回；下游提供 <strong>PC 商城 + PE（H5）+ 管理后台 + 用户中心</strong> 的全链路解决方案。</p>
<p>适用于：自动发卡商家、数字商品分销平台、虚拟主机服务商、多渠道电商货源整合平台。</p>
<hr>
<h2>二、我们解决什么问题</h2>
<table>
 <thead>
  <tr>
   <th>痛点</th>
   <th>小氢云怎么做</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>卡密/虚拟商品需人工发货</td>
   <td>对接上游货源，下单后自动交单、自动回卡</td>
  </tr>
  <tr>
   <td>多家货源接口不统一</td>
   <td>内置多家 SUP 对接 + <strong>插件化货源</strong>，JS 钩子扩展新渠道</td>
  </tr>
  <tr>
   <td>只有网页，移动端体验差</td>
   <td><strong>PC + PE（H5）+ 用户中心</strong> 多端一致</td>
  </tr>
  <tr>
   <td>想开分站/招供货商</td>
   <td><strong>多租户分站</strong> + <strong>供货商入驻审核</strong> 双模式</td>
  </tr>
  <tr>
   <td>闲鱼/91 卡券等外部渠道</td>
   <td><strong>闲管家、阿奇索</strong> 商品/订单/发货同步</td>
  </tr>
  <tr>
   <td>卖主机/建站服务</td>
   <td>内置<strong>虚拟主机面板</strong>（文件、SSL、数据库、Go 部署）</td>
  </tr>
  <tr>
   <td>客服、运营人力成本高</td>
   <td><strong>在线客服 + AI 助手 + QQ 官方机器人</strong> 全渠道服务</td>
  </tr>
  <tr>
   <td>从旧系统迁移困难</td>
   <td><strong>数据转移</strong>（含小储云 AI 智能迁库）</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>三、核心架构</h2>
<h3>技术栈</h3>
<ul>
 <li><strong>后端</strong>: Go + Gin 框架 + MySQL + Redis</li>
 <li><strong>管理后台</strong>: Vue 3 + Vite + Ant Design Vue 4</li>
 <li><strong>用户中心</strong>: Vue 3 + Ant Design Vue 4</li>
 <li><strong>PC 商城</strong>: Vue 3 + Ant Design Vue 4 + Element Plus</li>
 <li><strong>PE 移动端（H5）</strong>: UniApp 构建 Web 产物，嵌入 Go 后端供手机浏览器访问</li>
 <li><strong>部署</strong>: 前后端分离，前端构建产物嵌入 Go 二进制，单文件部署</li>
</ul>
<h3>多端覆盖</h3>
<pre><code>                    ┌─────────────────────────────────┐
                    │     Go 后端（Gin + MySQL + Redis）│
                    │  订单 · 支付 · 货源 · 插件 · AI   │
                    └───────────────┬─────────────────┘
                                    │
        ┌───────────┬───────────┬───┴───┬───────────┬───────────┐
        ▼           ▼           ▼       ▼           ▼           ▼
    管理后台      用户中心      PC商城   PE(H5)    模板装修SSR   Android客服
    (admin)       (user)        (pc)               (可视化建站)   (员工端)</code></pre>
<table>
 <thead>
  <tr>
   <th>端</th>
   <th>用途</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>管理后台（admin）</td>
   <td>系统运营管理，含供货商后台</td>
  </tr>
  <tr>
   <td>用户中心（user）</td>
   <td>用户个人中心、订单管理、分站管理</td>
  </tr>
  <tr>
   <td>PC 商城（pc）</td>
   <td>面向消费者的电脑端商城</td>
  </tr>
  <tr>
   <td><strong>PE（H5）</strong></td>
   <td><strong>手机浏览器移动端商城</strong>（UniApp 构建 H5，非小程序）</td>
  </tr>
  <tr>
   <td>模板装修 SSR</td>
   <td>可视化拖拽建站，Go 整页输出</td>
  </tr>
  <tr>
   <td>Android 客服 App</td>
   <td>员工移动端接待（独立 App，非 PE）</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>四、功能全景</h2>
<h3>4.1 商城前台（消费者端 · PC + PE H5）</h3>
<h4>商品浏览与购买</h4>
<ul>
 <li>多级分类导航、商品搜索</li>
 <li>商品详情（富文本描述 + 自定义输入框 + 多级价格）</li>
 <li>会员等级差价、密价隐藏、限购策略</li>
 <li>购物车合并下单、批量下单</li>
 <li><strong>砍价活动</strong>：发起砍价、分享好友帮砍、动态追踪</li>
 <li><strong>秒杀活动</strong>：限时抢购、倒计时、库存管理</li>
 <li><strong>免费商品</strong>：零元购引流</li>
 <li><strong>优惠券</strong>：领取、核销、满减</li>
 <li><strong>幸运抽奖</strong>：大转盘抽奖、实物奖品发货</li>
</ul>
<h4>订单与支付</h4>
<ul>
 <li><strong>多种支付方式</strong>：余额支付、支付宝/微信在线支付、活动余额、独立支付接口、余额卡密充值</li>
 <li>余额充值（在线支付 + 卡密充值）</li>
 <li>余额转账、余额提现（审核流程）</li>
 <li>订单实时进度追踪 + 操作日志</li>
 <li>售后申请 + 在线客服聊天</li>
 <li>游客下单 + 查单密码验证</li>
</ul>
<h4>用户中心</h4>
<ul>
 <li>个人仪表盘（资产总览、订单概览）</li>
 <li>邮箱/手机号绑定（含极验滑块验证码）</li>
 <li><strong>实名认证</strong>：三要素认证（姓名+身份证+手机号），对接阿里云市场</li>
 <li>收货地址管理</li>
 <li>邀请管理 + 下级用户追踪 + 邀请佣金</li>
 <li>等级升级、消费任务、操作日志</li>
 <li>系统对接 / API 密钥管理</li>
 <li>快捷登录绑定（QQ/微信/支付宝等）</li>
 <li>每日签到</li>
</ul>
<hr>
<h3>4.2 自动发卡与货源生态</h3>
<p><strong>商品侧</strong></p>
<ul>
 <li>17 种下单输入框类型（账号、邮箱、SKU、主机配置、图片/KYC 上传等）</li>
 <li>多级分类、梯度价、会员等级价、密价、限购、历史价</li>
 <li>本地卡密库存 + 上游货源混合售卖</li>
 <li>商品审核流（供货商提交 → 管理员审核）</li>
</ul>
<p><strong>已对接 / 可对接货源</strong></p>
<table>
 <thead>
  <tr>
   <th>供应商</th>
   <th>类型</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>卡速售</td>
   <td>虚拟商品自动发卡</td>
  </tr>
  <tr>
   <td>卡卡云</td>
   <td>虚拟商品 + 商品订阅</td>
  </tr>
  <tr>
   <td>卡易信</td>
   <td>虚拟商品自动发卡</td>
  </tr>
  <tr>
   <td>商战货源</td>
   <td>虚拟商品自动发卡</td>
  </tr>
  <tr>
   <td>聚比价</td>
   <td>价格比价 + 货源</td>
  </tr>
  <tr>
   <td>小储云</td>
   <td>兼容 API 对接</td>
  </tr>
  <tr>
   <td>亿乐 SUP</td>
   <td>虚拟商品自动发卡 + 订单进度追踪</td>
  </tr>
  <tr>
   <td>直客 SUP</td>
   <td>供应商 API 模拟</td>
  </tr>
 </tbody>
</table>
<p>另支持 <strong>provider.* 货源插件</strong>，JS 钩子扩展任意上游 API。</p>
<p><strong>自动化运维（计划任务）</strong> 商品库存监控、价格监控、订单同步、自动上下架、货源余量告警、自动确认收货、余额监控告警、实名认证余量监控、数据库定时备份等。</p>
<hr>
<h3>4.3 模板装修：不写代码改前台</h3>
<p><strong>模板装修（new）</strong> 是独立的可视化建站系统：</p>
<ul>
 <li><strong>158+ 业务组件</strong>，另支持 Wot / Vant / Ant Design 皮肤库</li>
 <li><strong>手机 / 平板 / 桌面三端独立布局</strong>，同一 type 协议自适应</li>
 <li><strong>Go SSR 整页输出</strong>，SEO 友好，按钮/表单可真实交互</li>
 <li><strong>AI 装修助手</strong>：自然语言驱动页面搭建</li>
 <li>与旧版「网站装修」独立，数据存 <code>sky_template_decoration</code></li>
</ul>
<p>适合：品牌站、活动页、分站首页、差异化落地页。</p>
<hr>
<h3>4.4 虚拟主机系统</h3>
<p>集成轻量级虚拟主机管理功能，支持：</p>
<ul>
 <li>主机生命周期：开通、续费、升级、扩容、到期监控</li>
 <li>在线文件管理（新建/编辑/上传/解压/权限设置）</li>
 <li>PHP 版本切换、运行目录设置</li>
 <li>自定义域名绑定 + SSL 证书管理</li>
 <li>URL 重定向 / 伪静态配置</li>
 <li>数据库管理（在线 SQL 执行）</li>
 <li>内置应用商店（一键部署 Go 应用）</li>
 <li>访问日志 / 错误日志、服务器资源监控</li>
 <li>多服务器管理、宝塔 API 对接</li>
</ul>
<hr>
<h3>4.5 分站（多租户）系统</h3>
<p>支持子站点独立运营，每个分站拥有：</p>
<ul>
 <li>独立域名绑定 + HTTPS</li>
 <li>独立支付接口配置</li>
 <li>独立商品展示（可覆盖主站价格、自定义上下架）</li>
 <li>独立分类体系、独立会员等级定价</li>
 <li>子账号管理、收益统计、分站模板/主题切换</li>
</ul>
<hr>
<h3>4.6 电商平台对接</h3>
<h4>闲管家（闲鱼/鱼市场）</h4>
<ul>
 <li>多账号管理、店铺绑定</li>
 <li>商品同步（发布/下架/编辑）</li>
 <li>订单同步 + 自动/手动发货</li>
 <li>卡密与兑换链接回传</li>
 <li>商品价格/库存变更订阅</li>
</ul>
<h4>阿奇索（91卡券）</h4>
<ul>
 <li>与闲管家对标的功能矩阵</li>
 <li>直充下单 + 卡密下单双模式</li>
</ul>
<hr>
<h3>4.7 AI 能力</h3>
<table>
 <thead>
  <tr>
   <th>模块</th>
   <th>能力</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>管理端 AI 助理</strong></td>
   <td>后台对话助手，辅助运营查询与操作</td>
  </tr>
  <tr>
   <td><strong>AI 商品讲解员</strong></td>
   <td>PE 商品详情悬浮 AI，专属知识库 + 问题本</td>
  </tr>
  <tr>
   <td><strong>QQ 官方机器人</strong></td>
   <td>私聊内商品咨询、智能下单、查单、充值、支付跟进</td>
  </tr>
  <tr>
   <td><strong>数据转移 AI</strong></td>
   <td>小储云 → 小氢云智能字段映射迁库</td>
  </tr>
  <tr>
   <td><strong>MCP 对接</strong></td>
   <td>管理端可接 MCP 工具链，扩展 AI 能力</td>
  </tr>
  <tr>
   <td><strong>技能 / 记忆 / 用量</strong></td>
   <td>AI 技能管理、长期记忆、Token 用量统计</td>
  </tr>
 </tbody>
</table>
<p>客服关键词库与 AI 回复可共用，<strong>人工规则优先，大模型兜底</strong>。</p>
<hr>
<h3>4.8 在线客服</h3>
<ul>
 <li>WebSocket 实时聊天（PC/PE H5 前台 + 管理端）</li>
 <li>快捷回复、分组管理、关键词自动回复</li>
 <li>售后工单联动</li>
 <li><strong>Android 员工端 App</strong>：移动端接待、消息推送、快捷回复管理</li>
</ul>
<hr>
<h3>4.9 插件与应用商店</h3>
<p><strong>插件钩子系统</strong>（21 个钩子分组）让第三方开发者在不改 Go 核心的前提下扩展：</p>
<ul>
 <li>订单事件（支付成功、改价、自定义展示）</li>
 <li>自定义支付渠道（<code>payment.*</code>）</li>
 <li>货源插件（<code>provider.*</code> 十一钩子）</li>
 <li>附件上传、设置面板、Action 多端路由</li>
 <li>AES 加密分发 + 授权绑定，防拷贝</li>
</ul>
<hr>
<h3>4.10 管理后台（运营端）</h3>
<h4>商品管理</h4>
<ul>
 <li>商品全生命周期管理（创建/编辑/复制/删除/批量操作）</li>
 <li>多级分类树 + 批量分类导入</li>
 <li>货源对接管理（上游供应商 CRUD）</li>
 <li>商品审核流程（供货商提交 → 管理员审核）</li>
 <li>兑换卡密生成管理、商品卡密批量导入/导出</li>
 <li>密价管理、限购配置、历史价显示、批量调价</li>
 <li>商品操作日志全追踪</li>
</ul>
<h4>订单管理</h4>
<ul>
 <li>订单列表（高级搜索/筛选/排序/导出）</li>
 <li>订单详情 + 进度日志 + 回调日志</li>
 <li>手动补单、修改状态、更新备注</li>
 <li>售后管理（列表/统计/设置）</li>
 <li>客服系统（在线聊天/快捷回复/关键词自动回复）</li>
</ul>
<h4>供货商管理</h4>
<ul>
 <li>供货商入驻申请审核</li>
 <li>供货商列表 / 商品审核 / 订单查看</li>
 <li>供货商资料审核、结算与佣金</li>
 <li>强制实名拦截（未实名无法上架商品）</li>
</ul>
<h4>用户管理</h4>
<ul>
 <li>用户列表 + 编辑 + 余额调整 + 等级调整</li>
 <li>用户权限管理 + API 权限</li>
 <li>实名认证记录 + 超管解密</li>
 <li>余额操作日志 + 提现审核</li>
 <li>余额卡密管理（生成/发放/核销）</li>
</ul>
<h4>活动营销</h4>
<ul>
 <li>优惠券（创建/发放/核销/补贴/回收）</li>
 <li>秒杀活动（定时上下架）</li>
 <li>砍价配置（每商品独立规则）</li>
 <li>幸运抽奖（多活动多奖品 + 实物发货）</li>
</ul>
<h4>支付与财务</h4>
<ul>
 <li>支付接口管理（支付宝/微信/易支付等）</li>
 <li>支付方式配置与展示控制</li>
 <li>充值记录 / 支付回调日志</li>
</ul>
<h4>系统配置</h4>
<ul>
 <li>网站基础设置（标题/注册开关/登录控制等）</li>
 <li>安全配置（密码盐/JWT/操作密钥/IP黑名单）</li>
 <li>OAuth 聚合登录配置</li>
 <li>短信服务商（腾讯云 SMS）、邮件服务（SMTP）</li>
 <li>云存储（阿里云 OSS / 腾讯 COS / 七牛）</li>
 <li>数据库备份/恢复/迁移</li>
 <li>系统任务调度、服务器性能监控</li>
 <li>API 请求日志 / 操作日志</li>
 <li>缓存管理（Redis 一键清理）、敏感词过滤</li>
</ul>
<h4>员工与权限</h4>
<ul>
 <li>多角色管理（管理员/员工/供货商）</li>
 <li>员工账户 CRUD、API 粒度权限分配、菜单级权限控制</li>
</ul>
<h4>素材管理</h4>
<ul>
 <li>图片/视频分组上传、素材库统计</li>
</ul>
<hr>
<h2>五、系统亮点</h2>
<ol>
 <li><strong>全自动化发卡</strong>：用户下单 → 自动调上游 → 自动返回卡密，全程无人值守</li>
 <li><strong>多渠道货源整合</strong>：一个平台对接多家上游供应商，自动比价/切换</li>
 <li><strong>双端商城 + 全后台</strong>：PC 商城 + PE（H5）+ 管理后台 + 用户中心，一套系统全搞定</li>
 <li><strong>可扩展插件生态</strong>：支付/通知/货源均可插件化，不写核心也能接新渠道</li>
 <li><strong>可视化 SSR 建站</strong>：模板装修 + AI 助手，品牌站不用另找 CMS</li>
 <li><strong>虚拟主机集成</strong>：自带轻量级主机管理面板，不依赖第三方</li>
 <li><strong>电商平台对接</strong>：直接对接闲鱼/闲管家/91卡券，拓展销售渠道</li>
 <li><strong>分站（多租户）</strong>：一个主站可开无数个子站，独立运营独立结算</li>
 <li><strong>供货商模式</strong>：支持供货商入驻、自主上架、管理员审核，平台化运营</li>
 <li><strong>AI 加持</strong>：商品讲解、QQ 购物流、迁库助手，降低人力成本</li>
 <li><strong>高性能 Go 后端</strong>：单文件部署、协程并发、Redis 缓存、资源占用极低</li>
 <li><strong>丰富营销工具</strong>：砍价/秒杀/优惠券/抽奖/签到/密价，提升转化</li>
</ol>
<hr>
<h2>六、适用场景</h2>
<table>
 <thead>
  <tr>
   <th>场景</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>自动发卡商城</td>
   <td>Steam 卡、话费、会员账号、兑换码</td>
  </tr>
  <tr>
   <td>数字商品分销平台</td>
   <td>分站 + 密价 + 供货商 + 佣金结算</td>
  </tr>
  <tr>
   <td>虚拟主机 / 建站服务</td>
   <td>主机面板 + 域名 SSL + Go 部署</td>
  </tr>
  <tr>
   <td>多渠道整合</td>
   <td>自营 + 闲鱼 + 91 卡券 + 上游 SUP</td>
  </tr>
  <tr>
   <td>知识付费 / 网课代刷</td>
   <td>富文本商品 + 进度追踪 + 自定义输入</td>
  </tr>
  <tr>
   <td>从旧系统迁移</td>
   <td>小储云等同类系统 AI 辅助迁库</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>七、目标用户</h2>
<table>
 <thead>
  <tr>
   <th>用户类型</th>
   <th>典型需求</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>个人/小团队发卡商</td>
   <td>快速建站、对接货源、自动发货</td>
  </tr>
  <tr>
   <td>分销平台运营者</td>
   <td>分站、密价、供货商、佣金结算</td>
  </tr>
  <tr>
   <td>虚拟主机服务商</td>
   <td>主机面板 + 商城售卖一体化</td>
  </tr>
  <tr>
   <td>多渠道卖家</td>
   <td>闲鱼/91 卡券 + 自营站同步管理</td>
  </tr>
  <tr>
   <td>从旧系统迁移者</td>
   <td>小储云等迁到小氢，AI 辅助映射</td>
  </tr>
 </tbody>
</table>
<hr>
<p><em>小氢云商城系统 — 让虚拟商品生意更简单</em></p>]]></description><guid isPermaLink="false">/archives/post-c46df9ea1422</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-hqmR.jpg&amp;size=m" type="image/jpeg" length="25225"/><category>小氢云商城系统</category><pubDate>Mon, 22 Jun 2026 07:01:12 GMT</pubDate></item><item><title><![CDATA[如何让 AI 助理写插件？]]></title><link>https://bbs.ithxs.com/archives/019ec9af-d078-72c8-9043-ac7534c18f37</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E8%AE%A9%20AI%20%E5%8A%A9%E7%90%86%E5%86%99%E6%8F%92%E4%BB%B6%EF%BC%9F&amp;url=/archives/019ec9af-d078-72c8-9043-ac7534c18f37" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="1%E3%80%81%E7%A1%AE%E5%AE%9A%E4%BD%A0%E7%9A%84%E9%9C%80%E6%B1%82">1、确定你的需求</h1>
<p style="">比如你想做一个清理清理某个表数据的插件，就拿接口日志举例。</p>
<p style=""></p>
<p style="">接口日志的表叫 sky_log，这个可以问 AI 助理。</p>
<p style=""></p>
<p style=""><code>你可以给 AI 助理说：我想做一个清理 sky_log 表数据的插件，可以选择日期区间清理，要有二次确认对话框，要有数据统计和分组展示日志类型，支持选择分组进行清理！UI 要兼容手机端和电脑端</code></p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-SgRs.png&amp;size=m" width="1521px">
</figure>
<p style="">回车发送等 Ai 处理即可</p>
<p style=""></p>
<p style=""></p>
<h1 style="" id="2%E3%80%81%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B">2、开发过程</h1>
<p style="">已经在自动创建对应插件文件了</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-VCPA.png&amp;size=m" width="1181px">
</figure>
<h1 style="" id="3%E3%80%81%E7%9C%8B%E7%9C%8B%E6%95%88%E6%9E%9C">3、看看效果</h1>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-DLOj.png&amp;size=m" width="757px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-pmvt.png&amp;size=m" width="532px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-sdJp.png&amp;size=m" width="1300px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-xkmD.png&amp;size=m" width="1113px">
</figure>
<p style="">二次确认也有</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-pmkb.png&amp;size=m" width="781px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-wicy.png&amp;size=m" width="480px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-sprn.png&amp;size=m" width="1091px">
</figure>
<p style="">成功被删除了，后续有任何需求都可以给 AI 助理讲，让他给你开发！！！</p>
<p style=""></p>
<h1 style="" id="%E5%90%8E%E7%BB%AD%E6%8B%93%E5%B1%95">后续拓展</h1>
<p style="">你可以把插件下载下来然后去授权站开通开发者账号上传插件赚钱</p>
<p style="">把插件下载下来，然后去 <a href="https://auth-v1.ithxs.com" target="_blank" rel="">https://auth-v1.ithxs.com</a> 申请开通开发者账号就可以上架插件啦！</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-PPdZ.png&amp;size=m" width="1056px">
</figure>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019ec9af-d078-72c8-9043-ac7534c18f37</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-SgRs.png&amp;size=m" type="image/jpeg" length="9927"/><category>小氢云</category><category>教程</category><pubDate>Mon, 15 Jun 2026 05:37:50 GMT</pubDate></item><item><title><![CDATA[小氢云极曈：宝塔二进制部署]]></title><link>https://bbs.ithxs.com/archives/post-1780275236765</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E6%9E%81%E6%9B%88%EF%BC%9A%E5%AE%9D%E5%A1%94%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2&amp;url=/archives/post-1780275236765" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>本文面向「已拿到 Linux 安装包、准备在宝塔服务器上跑 jitong-service 单文件」的运维与开发者。从宝塔面板是什么、左侧菜单干什么，到数据库、进程守护、Nginx 反代、SSL、排错，逐步拆解。</p>
</blockquote>
<hr>
<h2>写在前面：你将得到什么</h2>
<p>部署完成后，你应当能：</p>
<ol>
 <li>用浏览器打开 <code>https://你的域名</code>，进入<strong>已嵌入二进制</strong>的管理后台；</li>
 <li>在被监控网站里嵌入采集脚本，数据写入 ClickHouse；</li>
 <li>服务崩溃或服务器重启后，<strong>进程守护</strong>自动拉起 <code>jitong-service</code>；</li>
 <li>公网只暴露 80/443（及可选的采集端口），API/WebSocket 走 Nginx 反代，不直接裸露。</li>
</ol>
<p>本系统后端是 <strong>Go 编译的单体二进制</strong> <code>jitong-service</code>（<code>cmd/allinone</code>），内含采集、查询 API、WebSocket、管理端静态资源，<strong>不需要</strong>在服务器上再装 Node、PHP 或单独部署前端。</p>
<hr>
<h2>第一部分：认识宝塔面板（BT Panel）</h2>
<h3>1.1 宝塔是什么</h3>
<p><strong>宝塔 Linux 面板</strong>是一款 Web 化的服务器运维工具。它把常见的 Linux 命令（装 Nginx、绑域名、配 SSL、开防火墙、看磁盘）封装成左侧菜单里的可视化功能。</p>
<p>对你而言，宝塔主要承担四件事：</p>
<table>
 <thead>
  <tr>
   <th>角色</th>
   <th>在本文中的用途</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>文件管理器</strong></td>
   <td>上传 zip、解压、编辑 <code>config.yaml</code>、看 <code>app.log</code></td>
  </tr>
  <tr>
   <td><strong>软件商店</strong></td>
   <td>装 Nginx、装「进程守护管理器」、可选 Docker</td>
  </tr>
  <tr>
   <td><strong>网站</strong></td>
   <td>添加站点、配置反向代理、申请 Let's Encrypt 证书</td>
  </tr>
  <tr>
   <td><strong>安全 / 防火墙</strong></td>
   <td>放行 80、443、8888（面板）、按需放行采集端口</td>
  </tr>
  <tr>
   <td><strong>Docker（容器）</strong></td>
   <td>一键或手动跑 PostgreSQL、Redis、ClickHouse、NATS</td>
  </tr>
 </tbody>
</table>
<h3>1.2 登录后左侧菜单速览（11.x / 10.x 通用）</h3>
<p>不同小版本菜单名称可能略有差异，逻辑一致：</p>
<ul>
 <li><strong>首页</strong>：CPU、内存、磁盘、网络概览；适合确认机器是否吃得消（建议 <strong>4GB+ 内存</strong>，生产 <strong>8GB</strong> 更稳）。</li>
 <li><strong>网站</strong>：绑定域名 → Nginx 配置 → 反向代理 → SSL。</li>
 <li><strong>数据库</strong>：若用宝塔自带的 MySQL，本项与极曈无关；我们 PG/CH 走 Docker。</li>
 <li><strong>Docker / 容器</strong>：应用商店部署 PG、Redis、ClickHouse、NATS。</li>
 <li><strong>文件</strong>：<code>/www/wwwroot/jitong</code> 目录操作的主战场。</li>
 <li><strong>软件商店</strong>：安装 Nginx、<strong>进程守护管理器</strong>（Supervisor 插件）。</li>
 <li><strong>安全</strong>：系统防火墙、SSH 端口、可选「系统加固」。</li>
 <li><strong>计划任务</strong>：备份脚本、cron 清理日志（可选）。</li>
 <li><strong>终端</strong>：SSH 命令行（装 Docker、测端口、<code>chmod +x</code>）。</li>
</ul>
<h3>1.3 宝塔与「二进制部署」的关系</h3>
<p>很多人误以为宝塔只能跑 PHP。实际上：</p>
<ul>
 <li><strong>PHP 站点</strong>：宝塔擅长；</li>
 <li><strong>Go/Java 二进制</strong>：宝塔不负责编译，只负责 <strong>放文件 + 守护进程 + Nginx 反代</strong>，这正是本文路线。</li>
</ul>
<p>数据流可以记成：</p>
<blockquote>
 <p>用户浏览器 → 443/80（Nginx，宝塔「网站」） → 127.0.0.1:8081（jitong-service 查询+管理端） → 127.0.0.1:8082（WebSocket，需 Upgrade 头） → 127.0.0.1:8083（埋点采集，也可单独域名）</p>
 <p>jitong-service（单进程） → PostgreSQL / Redis / ClickHouse / NATS（建议本机 Docker，127.0.0.1 连接）</p>
</blockquote>
<hr>
<h2>第二部分：服务器与宝塔安装</h2>
<h3>2.1 硬件与系统</h3>
<table>
 <thead>
  <tr>
   <th>项目</th>
   <th>建议</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>系统</td>
   <td>CentOS 7+、AlmaLinux、Rocky、Ubuntu 20.04+、Debian 11+，<strong>64 位</strong></td>
  </tr>
  <tr>
   <td>CPU</td>
   <td>2 核+</td>
  </tr>
  <tr>
   <td>内存</td>
   <td>4GB 最低；ClickHouse + PG + 本服务建议 <strong>8GB</strong></td>
  </tr>
  <tr>
   <td>磁盘</td>
   <td>50GB+；事件量大时 ClickHouse 目录要单独规划</td>
  </tr>
  <tr>
   <td>架构</td>
   <td>x86_64（amd64）最常见；ARM 云服务器需 <strong>arm64</strong> 包（<code>build_backend.sh arm64</code>）</td>
  </tr>
 </tbody>
</table>
<h3>2.2 安装宝塔（未装面板时）</h3>
<p>SSH 登录服务器后执行官方脚本（以官网文档为准，以下为常见万能脚本示例）：</p>
<pre><code class="language-bash">if [ -f /usr/bin/curl ]; then
  curl -sSO https://download.bt.cn/install/install_panel.sh
else
  wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh
fi
bash install_panel.sh
</code></pre>
<p>安装结束终端会打印：</p>
<ul>
 <li><strong>外网面板地址</strong>：<code>http://IP:8888/xxxxxxxx</code></li>
 <li><strong>用户名 / 密码</strong></li>
</ul>
<p>务必立刻：</p>
<ol>
 <li>浏览器登录，按向导绑定宝塔账号（可选但推荐）；</li>
 <li><strong>面板设置</strong> → 修改默认端口、开启面板 SSL、限制面板 IP（安全加固）；</li>
 <li>在 <strong>安全</strong> 里放行你需要的端口（至少 SSH、80、443、8888 或你改过的面板端口）。</li>
</ol>
<h3>2.3 安装 Nginx（必做）</h3>
<p><strong>软件商店</strong> → 搜索 <strong>Nginx</strong> → 安装稳定版。</p>
<p>后面「网站」功能依赖 Nginx。若已装 OpenLiteSpeed 等，也可反代，但本文以 Nginx 为例（与宝塔文档一致）。</p>
<h3>2.4 安装 Docker（推荐，用于数据库）</h3>
<p>路径：<strong>Docker / 容器</strong> → 首次进入按提示安装 Docker 引擎。</p>
<p>也可命令行：</p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com | bash
systemctl enable docker
systemctl start docker
</code></pre>
<hr>
<h2>第三部分：用宝塔部署四大依赖（Docker）</h2>
<p>极曈单体二进制<strong>不自带</strong>数据库，需事先准备好：</p>
<ul>
 <li><strong>PostgreSQL</strong>：用户、订单、配置、租户等关系数据；</li>
 <li><strong>Redis</strong>：缓存、在线状态、实时相关；</li>
 <li><strong>ClickHouse</strong>：海量埋点事件分析；</li>
 <li><strong>NATS JetStream</strong>：采集到写入之间的消息队列。</li>
</ul>
<p>下面默认全部跑在<strong>同一台机</strong>的 Docker 里，应用配置里用 <code>127.0.0.1</code> 连接。</p>
<h3>3.1 PostgreSQL</h3>
<p><strong>方式 A：宝塔 Docker 应用商店</strong></p>
<ol>
 <li>Docker → 应用商店 → 搜索 <strong>PostgreSQL</strong> → 安装；</li>
 <li>建议配置：</li>
</ol>
<table>
 <thead>
  <tr>
   <th>项</th>
   <th>示例值</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>容器名</td>
   <td><code>jitong-postgres</code></td>
  </tr>
  <tr>
   <td>端口</td>
   <td><code>5432:5432</code></td>
  </tr>
  <tr>
   <td>数据卷</td>
   <td><code>/www/server/postgresql/data</code></td>
  </tr>
  <tr>
   <td>POSTGRES_USER</td>
   <td><code>tongji</code></td>
  </tr>
  <tr>
   <td>POSTGRES_PASSWORD</td>
   <td><strong>自行生成强密码</strong></td>
  </tr>
  <tr>
   <td>POSTGRES_DB</td>
   <td><code>tongji</code></td>
  </tr>
 </tbody>
</table>
<p><strong>方式 B：命令行</strong></p>
<pre><code class="language-bash">mkdir -p /www/server/postgresql/data
docker run -d \
  --name jitong-postgres \
  --restart always \
  -e POSTGRES_USER=tongji \
  -e POSTGRES_PASSWORD='你的强密码' \
  -e POSTGRES_DB=tongji \
  -p 5432:5432 \
  -v /www/server/postgresql/data:/var/lib/postgresql/data \
  postgres:16-alpine
</code></pre>
<p><strong>验证：</strong></p>
<pre><code class="language-bash">docker exec -it jitong-postgres psql -U tongji -d tongji -c "SELECT 1;"
</code></pre>
<blockquote>
 <p>PostgreSQL 15+ 若报 <code>permission denied for schema public</code>，需对业务用户授权 <code>public</code> schema（首次启动日志里会有 hint）。</p>
</blockquote>
<h3>3.2 Redis</h3>
<p>商店安装或：</p>
<pre><code class="language-bash">mkdir -p /www/server/redis/data
docker run -d \
  --name jitong-redis \
  --restart always \
  -p 6379:6379 \
  -v /www/server/redis/data:/data \
  redis:7-alpine \
  redis-server --requirepass '你的Redis密码' --appendonly yes
</code></pre>
<p>验证：<code>docker exec -it jitong-redis redis-cli -a 你的Redis密码 ping</code> → <code>PONG</code>。</p>
<h3>3.3 ClickHouse</h3>
<p>ClickHouse 需要能 <strong>建表、INSERT、复杂 SELECT</strong>（含 JSON 函数等）。</p>
<p>商店部署时注意：</p>
<table>
 <thead>
  <tr>
   <th>项</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>端口</td>
   <td>宿主机 <strong>19000:9000</strong>（避免与别的服务抢 9000）、<strong>8123:8123</strong> HTTP</td>
  </tr>
  <tr>
   <td>用户密码</td>
   <td>生产务必改掉默认</td>
  </tr>
  <tr>
   <td>关键环境变量</td>
   <td><code>CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1</code>（商店镜像若未带，需在容器「环境变量」里补并重启）</td>
  </tr>
 </tbody>
</table>
<p>命令行示例：</p>
<pre><code class="language-bash">mkdir -p /www/server/clickhouse/data
docker run -d \
  --name jitong-clickhouse \
  --restart always \
  -e CLICKHOUSE_USER=admin \
  -e CLICKHOUSE_PASSWORD='你的CH密码' \
  -e CLICKHOUSE_DB=default \
  -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \
  -p 19000:9000 \
  -p 8123:8123 \
  -v /www/server/clickhouse/data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:latest
</code></pre>
<p>验证：</p>
<pre><code class="language-bash">docker exec -it jitong-clickhouse clickhouse-client -u admin --password '你的CH密码' -q "SELECT 1"
</code></pre>
<h3>3.4 NATS（JetStream，必开）</h3>
<pre><code class="language-bash">mkdir -p /www/server/nats/jetstream
docker run -d \
  --name jitong-nats \
  --restart always \
  -p 4222:4222 \
  -p 8222:8222 \
  -v /www/server/nats/jetstream:/data/jetstream \
  nats:latest \
  -js -sd /data/jetstream
</code></pre>
<p>宝塔手动建容器时：<strong>执行命令</strong>填 <code>-js -sd /data/jetstream</code>，挂载目录 <code>/www/server/nats/jetstream:/data/jetstream</code>。</p>
<p>验证：<code>docker exec jitong-nats nats server info</code> 输出里应有 <code>JetStream: Enabled</code>。</p>
<h3>3.5 一键检查四个容器</h3>
<pre><code class="language-bash">docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep jitong
</code></pre>
<p>四个都在 <code>Up</code> 再往下走。</p>
<hr>
<h2>第四部分：部署 jitong-service 二进制</h2>
<h3>4.1 获取安装包</h3>
<p>官方渠道（如 auth-v1.ithxs.com 下载中心）获取：</p>
<ul>
 <li><code>jitong-service-vX.Y.Z-install.zip</code>（<strong>首次安装</strong>：含二进制 + <code>config/</code> + <code>start.sh</code> / <code>stop.sh</code>）</li>
</ul>
<p>解压后典型结构：</p>
<blockquote>
 <p>/www/wwwroot/jitong/ ├── jitong-service # Linux amd64/arm64 可执行文件 ├── config/ │ └── config.yaml ├── start.sh ├── stop.sh └── data/ # 需自建，放 WAL、上传等</p>
</blockquote>
<h3>4.2 在宝塔创建目录并上传</h3>
<ol>
 <li><strong>文件</strong> → 进入 <code>/www/wwwroot</code> → 新建文件夹 <code>jitong</code>；</li>
 <li>上传 zip → 右键 <strong>解压</strong> → 确认根目录直接是 <code>jitong-service</code>，而不是多套一层目录；</li>
 <li>新建 <code>data</code> 目录。</li>
</ol>
<h3>4.3 赋予执行权限</h3>
<p><strong>终端</strong>或 SSH：</p>
<pre><code class="language-bash">cd /www/wwwroot/jitong
chmod +x jitong-service start.sh stop.sh
file jitong-service   # 应显示 ELF 64-bit LSB executable
</code></pre>
<p>若报 <code>cannot execute: required file not found</code>：</p>
<ul>
 <li>常见原因：把 <strong>macOS 版</strong>二进制传上了 Linux，或架构不对（amd64 vs arm64）；</li>
 <li>处理：在开发机用 <code>build_backend.sh</code> 重新打 <strong>linux / linux-arm64</strong> 包再上传。</li>
</ul>
<h3>4.4 理解 config.yaml（逐项嚼碎）</h3>
<p>路径：<code>/www/wwwroot/jitong/config/config.yaml</code>。下面说明与<strong>本机 Docker</strong> 部署对应的改法。</p>
<h4>app</h4>
<ul>
 <li><code>version</code>：安装包已写入，一般不用手改。</li>
 <li><code>data_dir</code>（可选）：指定数据根，如 <code>/www/wwwroot/jitong/data</code>；不填则用系统缓存目录下的 <code>jitong</code>。</li>
</ul>
<h4>server（监听端口，被 Nginx 反代）</h4>
<table>
 <thead>
  <tr>
   <th>键</th>
   <th>默认</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>collector_addr</td>
   <td><code>:8083</code></td>
   <td>埋点、/collect、/t.gif</td>
  </tr>
  <tr>
   <td>query_addr</td>
   <td><code>:8081</code></td>
   <td>REST API + 嵌入的管理后台静态页</td>
  </tr>
  <tr>
   <td>ws_addr</td>
   <td><code>:8082</code></td>
   <td>WebSocket</td>
  </tr>
  <tr>
   <td>gin_mode</td>
   <td><code>release</code></td>
   <td>生产用 release</td>
  </tr>
 </tbody>
</table>
<p><strong>注意</strong>：改端口后，下文 Nginx <code>proxy_pass</code> 必须同步改。</p>
<h4>nats</h4>
<pre><code class="language-yaml">nats:
  url: "nats://127.0.0.1:4222"
  stream: "JITONG_EVENTS"
  subject_raw: "jitong.events.raw"
</code></pre>
<h4>clickhouse</h4>
<pre><code class="language-yaml">clickhouse:
  addr: "127.0.0.1:19000"   # 对应 Docker 19000:9000
  db: "default"
  user: "admin"
  password: "与容器一致"
</code></pre>
<h4>postgres</h4>
<pre><code class="language-yaml">postgres:
  dsn: "postgres://tongji:密码@127.0.0.1:5432/tongji?sslmode=disable"
</code></pre>
<h4>redis</h4>
<pre><code class="language-yaml">redis:
  addr: "127.0.0.1:6379"
  password: "与容器一致"
  db: 0
</code></pre>
<h4>auth（生产必改）</h4>
<pre><code class="language-yaml">auth:
  jwt_secret: "至少32位随机字符串"
  jwt_access_ttl: "24h"
</code></pre>
<p>也可用环境变量 <code>JWT_SECRET</code> 覆盖（见 <code>internal/config</code>）。</p>
<h4>collector</h4>
<ul>
 <li><code>wal_path</code>：可设为 <code>/www/wwwroot/jitong/data/collector.wal</code>，采集失败时本地容灾。</li>
</ul>
<h4>bootstrap（首次管理员）</h4>
<p>库中无管理员时，可用配置或环境变量：</p>
<ul>
 <li><code>BOOTSTRAP_ADMIN_ACCOUNT</code> / <code>BOOTSTRAP_ADMIN_PASSWORD</code></li>
</ul>
<p>否则默认账号见模板注释（<strong>上线务必改掉</strong>）。</p>
<h4>log</h4>
<p>生产建议 <code>level: "info"</code>，排错时临时 <code>debug</code>。</p>
<p>在宝塔 <strong>文件</strong> 里双击 yaml 编辑 → 保存。</p>
<h3>4.5 前台试运行（必做）</h3>
<pre><code class="language-bash">cd /www/wwwroot/jitong
./jitong-service
</code></pre>
<p>成功时类似：</p>
<blockquote>
 <p>postgres schema migrate ... 采集服务监听 :8083 查询服务监听 :8081 WebSocket 监听 :8082</p>
</blockquote>
<p>若失败，看报错关键词：</p>
<table>
 <thead>
  <tr>
   <th>报错方向</th>
   <th>处理</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>connection refused 5432/6379/4222/19000</td>
   <td>Docker 没起来或端口/密码错</td>
  </tr>
  <tr>
   <td>address already in use</td>
   <td><code>netstat -tlnp | grep 808</code> 换端口或杀占用进程</td>
  </tr>
  <tr>
   <td>permission denied schema public</td>
   <td>PG 授权</td>
  </tr>
  <tr>
   <td>ClickHouse auth</td>
   <td>用户密码、ACCESS_MANAGEMENT</td>
  </tr>
 </tbody>
</table>
<p>确认无误后 <code>Ctrl+C</code> 停掉，改由守护进程拉起。</p>
<hr>
<h2>第五部分：宝塔「进程守护管理器」常驻服务</h2>
<p>比 <code>nohup ./start.sh</code> 更稳的是 <strong>Supervisor</strong>（宝塔插件：<strong>进程守护管理器</strong>）。</p>
<h3>5.1 安装插件</h3>
<p><strong>软件商店</strong> → 搜索 <strong>进程守护管理器</strong> → 安装。</p>
<h3>5.2 添加守护进程</h3>
<p>点击 <strong>添加守护进程</strong>，建议填写：</p>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>值</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>名称</td>
   <td><code>jitong</code></td>
  </tr>
  <tr>
   <td>运行目录</td>
   <td><code>/www/wwwroot/jitong</code></td>
  </tr>
  <tr>
   <td>启动命令</td>
   <td><code>/www/wwwroot/jitong/jitong-service</code></td>
  </tr>
  <tr>
   <td>进程数量</td>
   <td>1</td>
  </tr>
  <tr>
   <td>用户</td>
   <td><code>root</code>（或单独建 <code>jitong</code> 用户降权）</td>
  </tr>
 </tbody>
</table>
<p><strong>不要</strong>用 <code>start.sh</code> 里 nohup 包一层给 Supervisor——Supervisor 自己会拉进程，双层容易误判。</p>
<h3>5.3 日志</h3>
<p>插件里可看 stdout/stderr；同时建议：</p>
<pre><code class="language-bash">tail -f /www/wwwroot/jitong/app.log   # 若你用 start.sh 测过
</code></pre>
<h3>5.4 开机自启</h3>
<p>守护进程配置正确且状态为 <strong>运行中</strong>，重启服务器后应自动恢复。可在面板 <strong>重启服务器</strong> 做一次验收。</p>
<h3>5.5 备选：systemd</h3>
<p>若不用 Supervisor，可建 <code>/etc/systemd/system/jitong.service</code>：</p>
<pre><code class="language-ini">[Unit]
Description=小氢云极曈监控 jitong-service
After=network.target docker.service

[Service]
Type=simple
WorkingDirectory=/www/wwwroot/jitong
ExecStart=/www/wwwroot/jitong/jitong-service
Restart=on-failure
RestartSec=5
Environment=CONFIG_FILE=/www/wwwroot/jitong/config/config.yaml

[Install]
WantedBy=multi-user.target
</code></pre>
<p>然后：</p>
<pre><code class="language-bash">systemctl daemon-reload
systemctl enable --now jitong
systemctl status jitong
</code></pre>
<p>宝塔与 systemd <strong>二选一</strong>即可，避免重复拉起两个进程。</p>
<hr>
<h2>第六部分：宝塔「网站」+ Nginx 反向代理</h2>
<p>管理端已打进二进制，对外只需一个域名指向 <strong>8081</strong>，WebSocket 走 <strong>8082</strong>，采集可走同域路径或子域。</p>
<h3>6.1 添加站点</h3>
<ol>
 <li><strong>网站</strong> → <strong>添加站点</strong>；</li>
 <li>域名：<code>monitor.example.com</code>（换成你的）；</li>
 <li>根目录：<code>/www/wwwroot/jitong</code>（纯静态即可，实际页面由 Go 提供）；</li>
 <li>PHP：纯静态 / 不创建 PHP。</li>
</ol>
<h3>6.2 配置反向代理（图形界面）</h3>
<p>站点 → <strong>设置</strong> → <strong>反向代理</strong> → <strong>添加反向代理</strong>：</p>
<p>可先加一条「主站」：</p>
<ul>
 <li>代理名称：<code>jitong-api</code></li>
 <li>目标 URL：<code>http://127.0.0.1:8081</code></li>
 <li>发送域名：<code>$host</code></li>
 <li>内容替换：留空</li>
</ul>
<p>但 <strong>WebSocket</strong> 和 <strong>采集路径</strong> 往往要单独 <code>location</code>，更推荐直接改配置文件。</p>
<h3>6.3 推荐：完整 Nginx 配置片段</h3>
<p>站点 → <strong>设置</strong> → <strong>配置文件</strong>，在 <code>server { ... }</code> 内合并（注意勿重复 <code>listen</code> 冲突）：</p>
<pre><code class="language-nginx">    # 管理后台 + API（单体二进制 query 端口）
    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60s;
        proxy_read_timeout 120s;
    }

    # WebSocket（实时在线等）
    location /ws {
        proxy_pass http://127.0.0.1:8082;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 3600s;
    }

    # 埋点采集（按你实际路由，常见 /collect、/t.gif 等）
    location /collect {
        proxy_pass http://127.0.0.1:8083;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /t.gif {
        proxy_pass http://127.0.0.1:8083;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
</code></pre>
<p>保存后 <strong>重载配置</strong>。宝塔会执行 <code>nginx -t</code>，失败则根据提示改语法。</p>
<h3>6.4 SSL 证书</h3>
<p>站点 → <strong>SSL</strong> → <strong>Let's Encrypt</strong> → 填邮箱 → 申请 → 开启 <strong>强制 HTTPS</strong>。</p>
<p>证书只挂在 Nginx；Go 进程仍监听内网 HTTP 端口，这是正常架构。</p>
<h3>6.5 采集脚本里的域名</h3>
<p>登录后台 → 站点管理 → 复制追踪代码，把脚本地址改成你的公网域名，例如：</p>
<pre><code class="language-html">&lt;script&gt;
(function () {
  var s = document.createElement('script');
  s.src = 'https://monitor.example.com/collect/jitong.js';
  s.async = true;
  document.head.appendChild(s);
})();
&lt;/script&gt;
</code></pre>
<p>确保 <code>location /collect</code> 已反代到 8083，且防火墙允许 HTTPS 出站访问。</p>
<hr>
<h2>第七部分：防火墙与安全</h2>
<h3>7.1 宝塔「安全」防火墙</h3>
<p>建议：</p>
<table>
 <thead>
  <tr>
   <th>端口</th>
   <th>是否对公网开放</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>80 / 443</td>
   <td>是</td>
   <td>网站入口</td>
  </tr>
  <tr>
   <td>8888（或自定义面板端口）</td>
   <td>限制 IP</td>
   <td>面板仅自己办公室 IP</td>
  </tr>
  <tr>
   <td>8081 / 8082</td>
   <td><strong>否</strong></td>
   <td>只本机 + Nginx</td>
  </tr>
  <tr>
   <td>8083</td>
   <td>视情况</td>
   <td>若采集不走 Nginx 直打 8083 才开；推荐只走 443 反代</td>
  </tr>
  <tr>
   <td>5432 / 6379 / 4222 / 19000</td>
   <td><strong>否</strong></td>
   <td>数据库绝不暴露公网</td>
  </tr>
 </tbody>
</table>
<h3>7.2 云厂商安全组</h3>
<p>阿里云/腾讯云等控制台还要放行 80/443，与宝塔防火墙是<strong>两层</strong>，都要开。</p>
<h3>7.3 生产安全清单</h3>
<ul>
 <li><input disabled type="checkbox"> 修改 <code>jwt_secret</code>、数据库密码、Redis/CH 密码</li>
 <li><input disabled type="checkbox"> 修改默认管理员密码</li>
 <li><input disabled type="checkbox"> 面板改端口 + 绑定访问 IP</li>
 <li><input disabled type="checkbox"> 定期备份 PG 与 <code>config.yaml</code>、<code>data/</code></li>
 <li><input disabled type="checkbox"> <code>gin_mode: release</code>，关闭不必要的 debug 接口</li>
</ul>
<hr>
<h2>第八部分：验收与日常运维</h2>
<h3>8.1 本机探测</h3>
<pre><code class="language-bash">curl -s http://127.0.0.1:8081/healthz
curl -I http://127.0.0.1:8081/
</code></pre>
<h3>8.2 域名访问</h3>
<p>浏览器打开 <code>https://monitor.example.com</code>，应出现登录页；首次按向导创建管理员。</p>
<h3>8.3 看指标</h3>
<pre><code class="language-bash">curl -s http://127.0.0.1:8081/metrics   # Prometheus，可按需限制访问
</code></pre>
<h3>8.4 升级（仅替换二进制）</h3>
<ol>
 <li>宝塔 <strong>进程守护</strong> 里停止 <code>jitong</code>；</li>
 <li>备份：<code>cp jitong-service jitong-service.bak</code>；</li>
 <li>上传 <code>jitong-service-vX.Y.Z-update.zip</code> 解压覆盖二进制；</li>
 <li><code>chmod +x jitong-service</code>；</li>
 <li>启动守护进程；</li>
 <li>后台也可用「在线升级」功能（若已接授权站）。</li>
</ol>
<p><strong>不要</strong>覆盖你已改好的 <code>config/config.yaml</code>，除非 release note 要求新增字段。</p>
<h3>8.5 备份（计划任务）</h3>
<p>在 <strong>计划任务</strong> 添加 Shell，每日备份：</p>
<ul>
 <li><code>docker exec jitong-postgres pg_dump ...</code></li>
 <li>打包 <code>/www/wwwroot/jitong/config</code> 与 <code>data</code></li>
</ul>
<hr>
<h2>第九部分：常见问题（嚼碎版）</h2>
<h3>Q1：面板里网站 502 Bad Gateway</h3>
<ol>
 <li><code>jitong-service</code> 是否在跑（守护进程状态）；</li>
 <li><code>127.0.0.1:8081</code> 能否 <code>curl</code>；</li>
 <li>Nginx 错误日志：网站 → 日志 → 错误日志；</li>
 <li>是否改了端口却未改 <code>proxy_pass</code>。</li>
</ol>
<h3>Q2：WebSocket 连不上</h3>
<p>必须单独 <code>location /ws</code> 且含 <code>Upgrade</code>、<code>Connection</code>；不能用普通 HTTP 反代规则糊在一起。</p>
<h3>Q3：有 PV 但报表为空</h3>
<p>查 NATS、ClickHouse 容器日志；看采集请求是否 200；浏览器 Network 看 <code>/collect</code> 是否打到正确域名。</p>
<h3>Q4：二进制 Permission denied</h3>
<p><code>chmod +x</code>；SELinux 环境可对目录 <code>chcon</code> 或临时 <code>setenforce 0</code> 试排（生产用正确上下文）。</p>
<h3>Q5：Supervisor 报「进程结束太快」</h3>
<p>说明启动命令错或二进制立刻崩溃；前台 <code>./jitong-service</code> 看 stderr。</p>
<h3>Q6：宝塔 start.sh 与文档不一致</h3>
<p>打包脚本生成的 <code>start.sh</code> 用 <code>command -v nohup</code>，避免硬编码 <code>/bin/nohup</code> 在部分系统不存在。生产推荐 <strong>进程守护</strong> 直接跑二进制。</p>
<hr>
<h2>第十部分：端口与架构一图读懂</h2>
<blockquote>
 <pre><code>                ┌─────────────────────────────────────┐
                │           宝塔面板 (8888)            │
                │  文件 / 网站 / Docker / 进程守护      │
                └─────────────────────────────────────┘
                                  │
</code></pre>
 <p>Internet ──► :443 Nginx (宝塔网站) ──┼──► :8081 jitong-service (API+Admin) │ ├──► :8082 WebSocket │ └──► :8083 Collector │ └──► Docker 127.0.0.1 ├── 5432 PostgreSQL ├── 6379 Redis ├── 19000 ClickHouse └── 4222 NATS JetStream</p>
</blockquote>
<hr>
<h2>结语</h2>
<p>宝塔的价值在于：<strong>你不需要成为 Linux 专家</strong>，也能完成「依赖用 Docker 管好、业务用二进制 + Supervisor 管好、入口用 Nginx + SSL 管好」这条标准生产路径。极曈监控把前后端打进一个 <code>jitong-service</code>，部署面比传统「前端 dist + 后端 jar」更简单，只要把 <strong>四个依赖容器</strong> 和 <strong>一份 config.yaml</strong> 配对正确，剩下的就是反代与守护进程这两层皮。</p>
<p>遇到具体报错，优先看：<code>jitong-service</code> 前台输出、Nginx 错误日志、对应 <code>docker logs jitong-*</code>。</p>
<hr>
<p><strong>文档版本</strong>：2.0（二进制 + 宝塔嚼碎版）
 <br>
 <strong>适用产品</strong>：小氢云极曈监控 jitong-service
 <br>
 <strong>最后更新</strong>：2026-06-01</p>]]></description><guid isPermaLink="false">/archives/post-1780275236765</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-mDeK.jpg&amp;size=m" type="image/jpeg" length="78650"/><category>教程</category><pubDate>Mon, 1 Jun 2026 00:54:08 GMT</pubDate></item><item><title><![CDATA[小储云主机：工作流完全指南]]></title><link>https://bbs.ithxs.com/archives/post-1780274715377</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E5%82%A8%E4%BA%91%E4%B8%BB%E6%9C%BA%EF%BC%9A%E5%B7%A5%E4%BD%9C%E6%B5%81%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97&amp;url=/archives/post-1780274715377" width="1" height="1" alt="" style="opacity:0;">
<h1>自定义工作流完全指南：从零到一键部署</h1>
<blockquote>
 <p>本文档级别：<strong>嚼碎了喂嘴里</strong>。假设你从未写过脚本，按顺序读、按顺序做，就能写出和系统内置「一键部署小氢云脚本」同等级的自动化流水线。</p>
</blockquote>
<hr>
<h2>目录</h2>
<ol>
 <li><a href="https://bbs.ithxs.com#1-30-%E7%A7%92%E6%90%9E%E6%87%82%E8%BF%99%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88">30 秒搞懂：这到底是什么</a></li>
 <li><a href="https://bbs.ithxs.com#2-%E7%AE%A1%E7%90%86%E7%AB%AF%E7%AC%AC%E4%B8%80%E6%AC%A1%E6%89%93%E5%BC%80%E7%BC%96%E8%BE%91%E5%99%A8%E9%80%90%E6%AD%A5%E7%82%B9%E5%87%BB">管理端：第一次打开编辑器（逐步点击）</a></li>
 <li><a href="https://bbs.ithxs.com#3-%E8%84%9A%E6%9C%AC%E8%BF%90%E8%A1%8C%E7%9A%84%E5%BA%95%E5%B1%82%E6%9C%BA%E5%88%B6">脚本运行的底层机制（知道这些就不踩坑）</a></li>
 <li><a href="https://bbs.ithxs.com#4-%E5%91%BD%E5%90%8D%E4%B8%8E%E5%8F%82%E6%95%B0%E4%B8%80%E5%BC%A0%E8%A1%A8%E8%AE%B0%E7%89%A2">命名与参数：一张表记牢</a></li>
 <li><a href="https://bbs.ithxs.com#5-host_id-%E4%B8%8E-host_source">host_id 与 host_source：最容易搞混的点</a></li>
 <li><a href="https://bbs.ithxs.com#6-%E7%AC%AC%E4%B8%80%E4%B8%AA%E8%83%BD%E8%B7%91%E7%9A%84%E8%84%9A%E6%9C%AC">第一个能跑的脚本（逐行解释）</a></li>
 <li><a href="https://bbs.ithxs.com#7-%E5%86%85%E7%BD%AE%E8%8A%82%E7%82%B9%E8%BF%94%E5%9B%9E%E5%80%BC%E5%AF%B9%E7%85%A7%E8%A1%A8">内置节点返回值对照表（必收藏）</a></li>
 <li><a href="https://bbs.ithxs.com#8-%E6%97%A5%E5%BF%97%E5%B7%A5%E5%85%B7">日志工具：直接复制粘贴</a></li>
 <li><a href="https://bbs.ithxs.com#9-%E9%80%9A%E7%94%A8%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0">通用工具函数：示例脚本逐函数讲解</a></li>
 <li><a href="https://bbs.ithxs.com#10-%E4%B8%8E%E7%94%A8%E6%88%B7%E5%BC%B9%E7%AA%97%E4%BA%A4%E4%BA%92">与用户弹窗交互：request_user_input 全解</a></li>
 <li><a href="https://bbs.ithxs.com#11-%E5%85%AB%E6%AD%A5%E9%83%A8%E7%BD%B2%E6%B5%81%E6%B0%B4%E7%BA%BF">八步部署流水线：每一步干什么、为什么、怎么写</a></li>
 <li><a href="https://bbs.ithxs.com#12-go-%E9%A1%B9%E7%9B%AE%E6%9F%A5%E9%87%8D">Go 项目查重：为什么不用 get_go_project_detail</a></li>
 <li><a href="https://bbs.ithxs.com#13-%E5%90%AF%E5%8A%A8%E5%A4%B1%E8%B4%A5%E8%87%AA%E5%8A%A8%E9%87%8D%E5%BB%BA">启动失败自动重建：容错逻辑</a></li>
 <li><a href="https://bbs.ithxs.com#14-%E9%82%AE%E4%BB%B6%E9%80%9A%E7%9F%A5%E6%A8%A1%E6%9D%BF">邮件通知模板</a></li>
 <li><a href="https://bbs.ithxs.com#15-%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8A%82%E7%82%B9">自定义节点：从创建到在工作流里调用</a></li>
 <li><a href="https://bbs.ithxs.com#16-%E5%8F%91%E5%B8%83%E4%B8%8A%E6%9E%B6%E6%8C%82%E5%88%B0%E7%94%A8%E6%88%B7%E4%B8%BB%E6%9C%BA">发布、上架、挂到用户主机</a></li>
 <li><a href="https://bbs.ithxs.com#17-%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C%E9%9D%A2%E6%9D%BF">执行结果面板：怎么看日志</a></li>
 <li><a href="https://bbs.ithxs.com#18-%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5%E7%9F%A9%E9%98%B5">故障排查矩阵</a></li>
 <li><a href="https://bbs.ithxs.com#19-%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E5%8C%BA--%E6%9C%80%E5%B0%8F%E5%8F%AF%E8%BF%90%E8%A1%8C%E6%A8%A1%E6%9D%BF">完整配置区 + 最小可运行模板</a></li>
 <li><a href="https://bbs.ithxs.com#20-%E9%99%84%E5%BD%95%E5%B8%B8%E7%94%A8%E5%86%85%E7%BD%AE%E8%8A%82%E7%82%B9%E9%80%9F%E6%9F%A5">附录：常用内置节点速查</a></li>
</ol>
<hr>
<h2>1. 30 秒搞懂：这到底是什么</h2>
<p><strong>一句话：</strong> 在管理后台写一段 JavaScript，里面调用一个个「节点函数」（如上传文件、建数据库），系统自动在宝塔面板上帮你执行。</p>
<p><strong>三个角色：</strong></p>
<table>
 <thead>
  <tr>
   <th>角色</th>
   <th>是什么</th>
   <th>举例</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>工作流</td>
   <td>一整段 JS 脚本 + 名称/状态</td>
   <td>「一键部署小氢云」</td>
  </tr>
  <tr>
   <td>内置节点</td>
   <td>系统写好的函数，内部调宝塔 API</td>
   <td><code>upload_file({...})</code></td>
  </tr>
  <tr>
   <td>自定义节点</td>
   <td>你自己扩展的函数</td>
   <td><code>my_api_call({...})</code></td>
  </tr>
 </tbody>
</table>
<p><strong>执行时发生什么：</strong></p>
<ol>
 <li>你点「运行」或用户在主机详情点快捷按钮</li>
 <li>服务端用 <strong>Goja</strong> 引擎跑你的 JS</li>
 <li>每调用一次 <code>xxx({...})</code>，记为一步，结果推送到前端</li>
 <li><code>console.log</code> 的内容出现在「控制台日志」</li>
 <li>某步失败 → <strong>立刻中断</strong>，后面不再跑（除非你自己用 <code>if</code> 吃掉错误）</li>
</ol>
<hr>
<h2>2. 管理端：第一次打开编辑器（逐步点击）</h2>
<h3>2.1 进入路径</h3>
<blockquote>
 <p>左侧菜单 → 工作流管理 → 右上角「新建工作流」 或直接访问：/workflow/editor</p>
</blockquote>
<h3>2.2 界面分区（从左到右）</h3>
<table>
 <thead>
  <tr>
   <th>区域</th>
   <th>作用</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>操作参考</strong>（左栏）</td>
   <td>按分类列出所有节点；搜索、展开详情、点「插入」</td>
  </tr>
  <tr>
   <td><strong>Monaco 编辑器</strong>（中间）</td>
   <td>写脚本；支持语法检查、Ctrl+S 保存、缩放</td>
  </tr>
  <tr>
   <td><strong>顶部工具栏</strong></td>
   <td>工作流名称、描述、执行主机 ID、保存/发布/运行</td>
  </tr>
 </tbody>
</table>
<h3>2.3 第一次运行（逐步）</h3>
<ol>
 <li>名称填：<code>测试检查文件</code></li>
 <li>中间编辑器<strong>全选删除</strong>，粘贴 <a href="https://bbs.ithxs.com#6-%E7%AC%AC%E4%B8%80%E4%B8%AA%E8%83%BD%E8%B7%91%E7%9A%84%E8%84%9A%E6%9C%AC">第 6 节</a> 的代码</li>
 <li>顶部 <strong>执行主机 ID</strong> 填一个真实主机 ID（在主机列表可查）</li>
 <li>点 <strong>运行</strong>（无需先保存，会直接跑编辑器里的内容）</li>
 <li>弹出 <strong>执行结果</strong> 抽屉：
  <ul>
   <li>上方：成功/失败</li>
   <li><strong>步骤明细</strong>：每一步调了哪个函数、参数、返回值</li>
   <li><strong>控制台日志</strong>：<code>console.log</code> 输出</li>
  </ul></li>
</ol>
<h3>2.4 保存与发布</h3>
<table>
 <thead>
  <tr>
   <th>按钮</th>
   <th>效果</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>保存草稿</td>
   <td>写入数据库，状态=草稿</td>
  </tr>
  <tr>
   <td>发布</td>
   <td>草稿 → 已启用（管理端可执行）</td>
  </tr>
  <tr>
   <td>列表页「上架」</td>
   <td>用户端可见、可绑定快捷入口</td>
  </tr>
 </tbody>
</table>
<blockquote>
 <p><strong>调试技巧：</strong> 改脚本 → 直接点运行 → 看日志 → 改完再点保存。不必每次保存。</p>
</blockquote>
<hr>
<h2>3. 脚本运行的底层机制</h2>
<h3>3.1 脚本被包在 IIFE 里</h3>
<p>系统自动把你的脚本包成：</p>
<pre><code class="language-javascript">(function(){
  // 你的脚本
})();
</code></pre>
<p>所以你可以在最外层用普通语句，<strong>不要用</strong> <code>return</code> 退出（除非配合 <code>end_workflow</code>）。</p>
<h3>3.2 host_id 自动注入</h3>
<p>当你在编辑器填了「执行主机 ID」，或用户在用户中心对某台主机跑工作流，系统会在脚本<strong>最开头</strong>插入：</p>
<pre><code class="language-javascript">var host_id = 123;
var hostId = 123;
// 然后才是你的脚本
</code></pre>
<p>并且会<strong>删掉</strong>你脚本里原有的 <code>const/let/var host_id = ...</code> 和 <code>hostId = ...</code> 赋值行，避免冲突。</p>
<h3>3.3 节点调用 = 一步操作</h3>
<pre><code class="language-javascript">var r = check_file_exists({ host_source: "auto", dirPath: "/", fileName: "a.txt" });
</code></pre>
<p>等价于：校验参数 → 调后端 → 宝塔查文件 → 把结果塞回 <code>r</code>。</p>
<p><strong>失败时：</strong> 抛异常，脚本中断，执行结果标记失败。</p>
<p><strong>成功时：</strong> 返回对象；若节点没定义特殊字段，自动补 <code>status: true, code: 200</code>。</p>
<h3>3.4 暂停与续跑（request_user_input）</h3>
<p>调用 <code>request_user_input</code> 时：</p>
<ol>
 <li>后端保存当前已完成的步骤快照</li>
 <li>向前端推送「等待输入」</li>
 <li>用户填完点确定 → 调用 resume 接口 → <strong>从断点继续</strong>，已完成步骤<strong>不会重复执行</strong></li>
</ol>
<p>用户点取消 → 返回 <code>{ cancelled: true }</code>，由你决定是 <code>end_workflow</code> 还是 <code>throw</code>。</p>
<h3>3.5 超时</h3>
<p>整段脚本最长 <strong>5 分钟</strong>。单步超时看各节点配置（自定义节点默认 30 秒）。</p>
<hr>
<h2>4. 命名与参数：一张表记牢</h2>
<table>
 <thead>
  <tr>
   <th>层级</th>
   <th>风格</th>
   <th>示例</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>传给节点的参数键</td>
   <td><strong>snake_case</strong></td>
   <td><code>host_id</code>, <code>host_source</code>, <code>file_name</code></td>
  </tr>
  <tr>
   <td>脚本内部变量</td>
   <td><strong>camelCase</strong>（推荐）</td>
   <td><code>hostInfo</code>, <code>siteRootPath</code></td>
  </tr>
  <tr>
   <td>节点函数名</td>
   <td><strong>snake_case</strong></td>
   <td><code>check_file_exists</code>, <code>create_go_project</code></td>
  </tr>
 </tbody>
</table>
<p><strong>铁律：</strong> 参数对象里的键名必须和操作参考里一致，写错键 = 参数传不进去。</p>
<hr>
<h2>5. host_id 与 host_source</h2>
<h3>5.1 两种 host_source</h3>
<table>
 <thead>
  <tr>
   <th>值</th>
   <th>含义</th>
   <th>典型场景</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>"auto"</code></td>
   <td>用执行上下文的主机</td>
   <td>用户中心、编辑器已填主机 ID</td>
  </tr>
  <tr>
   <td><code>"custom"</code></td>
   <td>脚本里显式传 <code>host_id</code></td>
   <td>管理端批量、示例部署脚本</td>
  </tr>
 </tbody>
</table>
<h3>5.2 auto 模式下路径怎么拼</h3>
<p>设当前主机名为 <code>shop.example.com</code>，站点根目录固定为：</p>
<p><code>/www/wwwroot/shop.example.com</code></p>
<p>此时：</p>
<pre><code class="language-javascript">check_file_exists({
  host_source: "auto",
  dirPath: "/",              // → /www/wwwroot/shop.example.com
  fileName: "index.php"
});

check_file_exists({
  host_source: "auto",
  dirPath: "public/uploads", // → /www/wwwroot/shop.example.com/public/uploads
  fileName: "logo.png"
});
</code></pre>
<h3>5.3 custom 模式</h3>
<pre><code class="language-javascript">check_file_exists({
  host_source: "custom",
  host_id: hostId,           // 数字或变量
  dirPath: "/www/wwwroot/shop.example.com",
  fileName: "index.php"
});
</code></pre>
<h3>5.4 本地文件 upload 路径（与 host 无关）</h3>
<table>
 <thead>
  <tr>
   <th>写法</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>@/app.zip</code></td>
   <td>管理端 <strong>doc 目录</strong> 下的 <code>app.zip</code></td>
  </tr>
  <tr>
   <td><code>/app.zip</code></td>
   <td>同上（文件管理器虚拟路径）</td>
  </tr>
  <tr>
   <td>绝对路径</td>
   <td>服务器上真实路径（需在 doc 目录内）</td>
  </tr>
 </tbody>
</table>
<p><strong>上传前：</strong> 把 zip 放到 doc 目录，否则报「本地文件不存在」。</p>
<hr>
<h2>6. 第一个能跑的脚本</h2>
<p><strong>复制下面整段 → 粘贴到编辑器 → 填执行主机 ID → 运行。</strong></p>
<pre><code class="language-javascript">// ===== 第 1 步：检查站点根目录有没有 index.php =====
var result = check_file_exists({
  host_source: "auto",
  dirPath: "/",
  fileName: "index.php"
});

// ===== 第 2 步：打印结果（在「控制台日志」里看）=====
console.log("API 是否成功:", result.status);
console.log("文件是否存在:", result.found);   // 注意：字段名是 found，不是 exists

// ===== 第 3 步：根据结果分支 =====
if (result.found) {
  console.log("index.php 已存在，无需处理");
} else {
  console.log("index.php 不存在，可以在这里继续 upload_file 或 create_file");
}
</code></pre>
<p><strong>逐行解释：</strong></p>
<ul>
 <li><code>check_file_exists</code>：节点函数，查远程目录里有没有某文件</li>
 <li><code>host_source: "auto"</code>：用当前主机，不用手写路径里的主机名</li>
 <li><code>dirPath: "/"</code>：站点根目录（不是服务器根 <code>/</code>）</li>
 <li><code>fileName</code>：只写文件名，不含路径</li>
 <li><code>result.found</code>：<code>true</code>=找到，<code>false</code>=没找到（<strong>没找到不算 API 失败</strong>）</li>
 <li><code>result.status</code>：API 调用本身是否成功</li>
</ul>
<hr>
<h2>7. 内置节点返回值对照表</h2>
<blockquote>
 <p>写 <code>if</code> 判断前先看这张表，<strong>不同节点字段名不一样</strong>。</p>
</blockquote>
<h3>7.1 通用成功</h3>
<p>大多数写操作（create、delete、unzip…）成功时：</p>
<pre><code class="language-json">{ "status": true, "code": 200 }
</code></pre>
<h3>7.2 check_file_exists</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>类型</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>status</td>
   <td>bool</td>
   <td>API 成功</td>
  </tr>
  <tr>
   <td>code</td>
   <td>int</td>
   <td>200=找到，404=未找到</td>
  </tr>
  <tr>
   <td><strong>found</strong></td>
   <td>bool</td>
   <td><strong>true=文件存在</strong></td>
  </tr>
 </tbody>
</table>
<pre><code class="language-javascript">if (result.found) { /* 存在 */ }
if (!result.found) { /* 不存在，但 API 可能仍 status=true */ }
</code></pre>
<h3>7.3 database_exists</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>value</td>
   <td><strong>true/false 库是否存在</strong></td>
  </tr>
  <tr>
   <td>status</td>
   <td>查询是否成功</td>
  </tr>
 </tbody>
</table>
<pre><code class="language-javascript">// 必须用 value，不要直接用 status 判断库是否存在
function isDatabaseExists(r) {
  return !!(r &amp;&amp; r.value === true);
}
</code></pre>
<h3>7.4 upload_file</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>status</td>
   <td>是否成功</td>
  </tr>
  <tr>
   <td>progress</td>
   <td>0-100，完成时 100</td>
  </tr>
  <tr>
   <td>file_size</td>
   <td>字节数</td>
  </tr>
  <tr>
   <td>remote_path</td>
   <td>远程完整路径</td>
  </tr>
 </tbody>
</table>
<p>执行过程中前端可通过 WebSocket 看实时上传进度。</p>
<h3>7.5 port_rule_exists</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>exists</td>
   <td>端口是否已在防火墙放行</td>
  </tr>
 </tbody>
</table>
<h3>7.6 get_go_project_detail</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>found</td>
   <td>项目是否存在</td>
  </tr>
  <tr>
   <td>status</td>
   <td>found=true 时为 true</td>
  </tr>
 </tbody>
</table>
<p><strong>注意：</strong> 部署脚本<strong>故意不用</strong>此节点做查重，见 <a href="https://bbs.ithxs.com#12-go-%E9%A1%B9%E7%9B%AE%E6%9F%A5%E9%87%8D">第 12 节</a>。</p>
<h3>7.7 start_go_project</h3>
<p>失败时<strong>不抛错</strong>，返回 <code>{ status: false, code: 404, error: "..." }</code>，需自己判断。</p>
<h3>7.8 request_user_input</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>cancelled</td>
   <td>用户是否取消</td>
  </tr>
  <tr>
   <td>value</td>
   <td>用户输入（confirm 类型为 true/false）</td>
  </tr>
 </tbody>
</table>
<h3>7.9 bind_user_domains_local</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>skipped</td>
   <td>是否全部重复跳过</td>
  </tr>
  <tr>
   <td>added</td>
   <td>本次新增域名数组</td>
  </tr>
  <tr>
   <td>total_count</td>
   <td>绑定后总数</td>
  </tr>
  <tr>
   <td>local_only</td>
   <td>恒为 true（只写本系统，不调宝塔）</td>
  </tr>
 </tbody>
</table>
<h3>7.9 replace_config_content</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>modified_keys</td>
   <td>成功替换的点分路径列表，如 <code>["database.host","app.base_url"]</code></td>
  </tr>
 </tbody>
</table>
<hr>
<h2>8. 日志工具</h2>
<p><strong>直接复制到脚本顶部</strong>，和示例脚本保持一致：</p>
<pre><code class="language-javascript">var logSequence = 0;

function padLogIndex(value) {
  return value &lt; 10 ? "0" + value : String(value);
}

// 主步骤：[01] [02] … 自动递增
function logInfo(message) {
  logSequence += 1;
  console.log("[" + padLogIndex(logSequence) + "] " + message);
}

// 子步骤：树形缩进，不占主序号
function logDetail(message) {
  console.log("         └─ " + message);
}

// 阶段标题：固定 01~08，和流程图对齐
function logPhase(phaseNo, title) {
  console.log("[" + padLogIndex(phaseNo) + "] " + title);
}
</code></pre>
<p><strong>输出效果：</strong></p>
<blockquote>
 <p>[01] 初始化部署上下文 [01] 主机: demo.com | 用户: zhangsan └─ siteRootPath: /www/wwwroot/demo.com [02] 检测并上传部署包 [03] 远程包已存在，跳过上传</p>
</blockquote>
<hr>
<h2>9. 通用工具函数</h2>
<p>以下全部来自 <code>一键部署小氢云脚本.js</code>，建议原样复制。</p>
<h3>9.1 sanitizeGoProjectName / sanitizeDbName</h3>
<p>宝塔 Go 项目名、MySQL 库名<strong>不能含 <code>.</code> 和 <code>-</code></strong>：</p>
<pre><code class="language-javascript">function sanitizeGoProjectName(hostName) {
  return String(hostName || "").replace(/\./g, "_").replace(/-/g, "_");
}
// shop.example.com → shop_example_com
</code></pre>
<p>数据库名规则相同，用 <code>sanitizeDbName</code>。</p>
<h3>9.2 randomDbPassword</h3>
<p>新建库且主机记录无密码时，生成 16 位随机密码（去掉易混淆字符 0/O/1/l/I）。</p>
<h3>9.3 normalizeDomainInput</h3>
<p>用户可能输入 <code>https://shop.com/</code> 或 <code>shop.com/path</code>，统一成 <code>shop.com</code>：</p>
<ul>
 <li>去首尾空格、末尾 <code>/</code></li>
 <li>去 <code>http://</code> / <code>https://</code></li>
 <li>去路径部分</li>
</ul>
<h3>9.4 assertActionSuccess</h3>
<pre><code class="language-javascript">function assertActionSuccess(actionResult, actionName) {
  if (!actionResult || actionResult.status !== true) {
    throw new Error(actionName + " 失败: " + JSON.stringify(actionResult || { status: false }));
  }
  return actionResult;
}
</code></pre>
<p>用于 <code>create_database</code>、<code>replace_config_content</code> 等<strong>必须成功</strong>的步骤。</p>
<h3>9.5 isDatabaseExists</h3>
<pre><code class="language-javascript">function isDatabaseExists(checkResult) {
  return !!(checkResult &amp;&amp; checkResult.value === true);
}
</code></pre>
<hr>
<h2>10. 与用户弹窗交互</h2>
<h3>10.1 全部 input_type</h3>
<table>
 <thead>
  <tr>
   <th>input_type</th>
   <th>界面</th>
   <th>value 类型</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>text</td>
   <td>单行输入框</td>
   <td>字符串</td>
  </tr>
  <tr>
   <td>textarea</td>
   <td>多行</td>
   <td>字符串</td>
  </tr>
  <tr>
   <td>number</td>
   <td>数字框</td>
   <td>数字</td>
  </tr>
  <tr>
   <td>password</td>
   <td>密码框</td>
   <td>字符串</td>
  </tr>
  <tr>
   <td>select</td>
   <td>下拉（不可输入）</td>
   <td>选项 value</td>
  </tr>
  <tr>
   <td>combobox</td>
   <td>下拉 + 可手输</td>
   <td>字符串</td>
  </tr>
  <tr>
   <td>confirm</td>
   <td>确认框</td>
   <td>确认=true</td>
  </tr>
 </tbody>
</table>
<h3>10.2 示例脚本里的域名选择（完整逻辑）</h3>
<pre><code class="language-javascript">function requestBindDomain(hostInfo) {
  var boundDomains = parseBoundDomains(hostInfo);

  // SSL 开了就用 https
  var enableHttps = hostInfo &amp;&amp; (
    hostInfo.ssl_enabled === 1 || hostInfo.ssl_enabled === true ||
    hostInfo.ssl_force_https === 1 || hostInfo.ssl_force_https === true
  );
  var urlScheme = enableHttps ? "https" : "http";

  // 已有域名做成下拉选项
  var domainOptions = [];
  for (var i = 0; i &lt; boundDomains.length; i++) {
    domainOptions.push({ label: boundDomains[i], value: boundDomains[i] });
  }

  var inputResult = request_user_input({
    title: "绑定域名",
    content: boundDomains.length ? "请选择或输入域名" : "请输入要绑定的域名",
    input_type: "combobox",
    placeholder: "example.com",
    options: domainOptions,
    required: true,
    field_key: "domain"
  });

  if (inputResult.cancelled) {
    end_workflow({ message: "用户取消了域名输入" });  // 成功结束，不算失败
  }

  var selectedDomain = normalizeDomainInput(inputResult.value);
  if (!selectedDomain) throw new Error("域名不能为空");

  return {
    bindDomain: selectedDomain,
    appBaseUrl: urlScheme + "://" + selectedDomain
  };
}
</code></pre>
<h3>10.3 parseBoundDomains</h3>
<p><code>hostInfo.domains</code> 可能是 JSON 字符串、数组或逗号分隔文本——此函数统一解析成字符串数组。</p>
<hr>
<h2>11. 八步部署流水线</h2>
<p>下面按 <strong>[01]~[08]</strong> 逐步说明：做什么、判断条件、关键代码。</p>
<h3>[01] 初始化部署上下文</h3>
<p><strong>干什么：</strong></p>
<ol>
 <li><code>get_host_info</code> 拿主机名、端口、数据库信息、domains</li>
 <li><code>get_user_info</code> 拿用户名、邮箱（后面发邮件）</li>
 <li><code>requestBindDomain</code> 弹窗选域名 → 得到 <code>appBaseUrl</code></li>
 <li>算出所有路径变量</li>
</ol>
<p><strong>派生变量（记住公式）：</strong></p>
<pre><code class="language-javascript">var siteRootPath = "/www/wwwroot/" + hostInfo.host_name;
var remotePackagePath = siteRootPath + "/" + packageFileName;
var binaryFilePath = siteRootPath + "/" + binaryFileName;
var configFilePath = siteRootPath + "/config/config.yaml";
var goProjectName = sanitizeGoProjectName(hostInfo.host_name);
var servicePort = (hostInfo.port &amp;&amp; hostInfo.port &gt; 0) ? hostInfo.port : defaultPort;
</code></pre>
<h3>[02] 检测并上传部署包</h3>
<p><strong>流程图：</strong></p>
<blockquote>
 <p>check_file_exists(zip) ├─ found=true → 跳过上传 └─ found=false → upload_file → 再 check 一次校验</p>
</blockquote>
<p><strong>上传代码：</strong></p>
<pre><code class="language-javascript">uploadResult = upload_file({
  host_source: "custom",
  host_id: hostId,
  targetDir: siteRootPath,
  fileName: packageFileName,
  content_source: "local",
  content: localPackagePath   // 如 @/jjwk-service-v2.4.8-install.zip
});
</code></pre>
<p><strong>必须检查：</strong> <code>uploadResult.status === true &amp;&amp; uploadResult.progress === 100</code></p>
<h3>[03] 解压并校验二进制</h3>
<p><strong>何时解压：</strong></p>
<pre><code class="language-javascript">var shouldExtractPackage = !binaryExistsResult.found || (packageUploaded &amp;&amp; forceUnzipAfterUpload);
</code></pre>
<table>
 <thead>
  <tr>
   <th>情况</th>
   <th>行为</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>二进制不存在</td>
   <td>解压</td>
  </tr>
  <tr>
   <td>刚上传了新包且 forceUnzipAfterUpload=true</td>
   <td>强制覆盖解压</td>
  </tr>
  <tr>
   <td>二进制已存在且没传新包</td>
   <td>跳过</td>
  </tr>
 </tbody>
</table>
<p>解压后：<code>check_file_exists</code> 确认二进制存在 → <code>set_file_access</code> 设为 <code>www:755</code>。</p>
<h3>[04] 检查并初始化数据库</h3>
<blockquote>
 <p>database_exists(dbName) ├─ value=true → 跳过创建，从 hostInfo 读 db_name/db_account/db_password └─ value=false → create_database → 再 database_exists 校验</p>
</blockquote>
<p>库名默认 <code>sanitizeDbName(host_name)</code>。无密码则 <code>randomDbPassword(16)</code>。</p>
<h3>[05] 更新 config.yaml</h3>
<pre><code class="language-javascript">replace_config_content({
  format: "yaml",
  host_id: hostId,
  filePath: configFilePath,
  replacements: {
    "database.host": "127.0.0.1",
    "database.port": "3306",
    "database.username": databaseUser,
    "database.dbname": databaseName,
    "database.password": databasePassword,
    "app.base_url": appBaseUrl,
    "app.port": servicePort
  }
});
</code></pre>
<p><strong>注意：</strong> 键必须是 yaml 里的点分路径；任一键在文件里不存在 → <strong>整步失败</strong>。</p>
<h3>[06] 检查并放行端口</h3>
<blockquote>
 <p>port_rule_exists(port) ├─ exists=true → 跳过 └─ exists=false → set_port_rule → delay(2秒)</p>
</blockquote>
<p><code>create_go_project</code> 里可设 <code>release_firewall: true</code>，但示例脚本在 [06] 单独做，且创建项目时设 <code>release_firewall: false</code> 避免重复。</p>
<h3>[07] 注册并启动 Go 项目</h3>
<p>见 <a href="https://bbs.ithxs.com#12-go-%E9%A1%B9%E7%9B%AE%E6%9F%A5%E9%87%8D">第 12、13 节</a>。</p>
<h3>[08] 发送部署完成通知</h3>
<p>有邮箱则 <code>send_email</code>（<strong>异步</strong>，不阻塞）；无邮箱打日志跳过。</p>
<hr>
<h2>12. Go 项目查重</h2>
<p><strong>为什么不用 <code>get_go_project_detail</code>？</strong>
 <br>
 实测易误判「不存在」，导致跳过 <code>create_go_project</code>，后面 <code>start_go_project</code> 又失败。</p>
<p><strong>正确做法：</strong> 只用 <code>get_go_project_list</code>，封装 <code>findGoProject</code>：</p>
<ol>
 <li>用项目名、主机名、sanitize 后的主机名<strong>分别搜索</strong></li>
 <li>每条列表项用三种规则匹配：路径相同 / 项目名相同 / 主机名相同</li>
 <li>搜不到再<strong>全量拉 1000 条</strong>扫描</li>
</ol>
<p>创建后用 <code>verifyGoProjectExists</code>：<strong>最多重试 5 次，每次间隔 2 秒</strong>，等宝塔列表刷出来。</p>
<hr>
<h2>13. 启动失败自动重建</h2>
<pre><code class="language-javascript">var startGoProjectResult = start_go_project({ projectName: activeProjectName });

if (!startGoProjectResult || startGoProjectResult.status !== true) {
  // 启动失败 → 重新 create_go_project → verify → 再 start
  create_go_project({ /* 同参数 */ });
  verifyGoProjectExists(...);
  startGoProjectResult = start_go_project({ projectName: activeProjectName });
}
assertActionSuccess(startGoProjectResult, "start_go_project");
</code></pre>
<p><strong>另外：</strong> <code>bind_user_domains_local</code> 在启动前执行，把域名写入本系统主机记录（与宝塔绑定独立）。</p>
<hr>
<h2>14. 邮件通知模板</h2>
<pre><code class="language-javascript">send_email({
  to: userProfile.email,
  subject: "主机搭建通知 - " + hostInfo.host_name,
  body: "&lt;p&gt;主机 &lt;strong&gt;" + hostInfo.host_name + "&lt;/strong&gt; 工作流执行完成。&lt;/p&gt;" +
    "&lt;ul&gt;" +
    "&lt;li&gt;部署包: " + (packageExistsResult.found ? "已存在（跳过上传）" : "已上传") + "&lt;/li&gt;" +
    "&lt;li&gt;二进制: " + (/* 是否重新解压 */) + "&lt;/li&gt;" +
    "&lt;li&gt;数据库: " + databaseName + (databaseCreated ? "（新建）" : "（已存在）") + "&lt;/li&gt;" +
    "&lt;li&gt;站点域名: " + bindDomain + "（" + appBaseUrl + "）&lt;/li&gt;" +
    "&lt;li&gt;Go 项目: " + activeProjectName + "（端口 " + servicePort + "）&lt;/li&gt;" +
    "&lt;/ul&gt;"
});
</code></pre>
<p><code>send_email</code> 提交后立即继续，<strong>不等发信结果</strong>。</p>
<hr>
<h2>15. 自定义节点</h2>
<p>路径：<strong>自定义节点 → 新建</strong></p>
<h3>15.1 必填项</h3>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>action_key</td>
   <td>函数名，全局唯一，如 <code>sync_user_quota</code></td>
  </tr>
  <tr>
   <td>入参 Schema</td>
   <td>工作流里调用时要传哪些键</td>
  </tr>
  <tr>
   <td>出参 Schema</td>
   <td>文档用途</td>
  </tr>
  <tr>
   <td>执行脚本</td>
   <td>Goja JS</td>
  </tr>
 </tbody>
</table>
<h3>15.2 脚本内置对象</h3>
<pre><code class="language-javascript">// ctx.params     — 调用方传入的参数
// ctx.host_id    — 主机 ID
// ctx.base_url   — 有 host_id 时为宝塔面板地址，否则管理端地址
// ctx.api_path   — 节点配置的默认路径
// ctx.api_method — 默认 HTTP 方法
// ctx.admin_token / ctx.user_token — 按场景注入
</code></pre>
<h3>15.3 request / handle_response 模板</h3>
<pre><code class="language-javascript">var resp = request({
  url: ctx.api_path,
  method: ctx.api_method || "POST",
  headers: { "Content-Type": "application/json" },
  body: ctx.params,
  timeout: ctx.timeout_sec || 30
});

return handle_response(resp, function(json) {
  if (json.http_status &gt;= 400) {
    return { status: false, code: json.http_status, message: json.msg || "HTTP 失败" };
  }
  if (json.code !== undefined &amp;&amp; json.code !== 200) {
    return { status: false, code: json.code, message: json.msg || "业务失败" };
  }
  return { status: true, code: 200, data: json.data !== undefined ? json.data : json };
});
</code></pre>
<h3>15.4 在工作流里调用</h3>
<pre><code class="language-javascript">var r = sync_user_quota({ host_id: hostId, quota_mb: 1024 });
if (!r.status) throw new Error("同步失败");
</code></pre>
<p>启用节点后，左侧操作参考会出现该函数。</p>
<hr>
<h2>16. 发布、上架、挂到用户主机</h2>
<table>
 <thead>
  <tr>
   <th>步骤</th>
   <th>操作</th>
   <th>结果</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>1</td>
   <td>编辑器保存草稿</td>
   <td>脚本入库</td>
  </tr>
  <tr>
   <td>2</td>
   <td>发布</td>
   <td>status=已启用</td>
  </tr>
  <tr>
   <td>3</td>
   <td>列表页「上架」</td>
   <td>用户端可见</td>
  </tr>
  <tr>
   <td>4</td>
   <td>商品/快捷入口配置关联工作流</td>
   <td>用户主机详情出现按钮</td>
  </tr>
  <tr>
   <td>5</td>
   <td>用户点击</td>
   <td>自动注入 host_id，host_source 用 auto</td>
  </tr>
 </tbody>
</table>
<p><strong>导入导出：</strong> 列表页可导出 JSON（含 script），换环境导入。</p>
<hr>
<h2>17. 执行结果面板</h2>
<table>
 <thead>
  <tr>
   <th>区块</th>
   <th>看什么</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>执行状态</td>
   <td>成功/失败/执行中</td>
  </tr>
  <tr>
   <td>步骤明细</td>
   <td>每步 action_key、参数、返回值、耗时</td>
  </tr>
  <tr>
   <td>控制台日志</td>
   <td>console.log / logInfo / logDetail</td>
  </tr>
  <tr>
   <td>上传进度</td>
   <td>upload_file 实时百分比</td>
  </tr>
 </tbody>
</table>
<p>步骤为空？说明脚本里没调用任何节点函数（纯 console.log 不会出现步骤）。</p>
<hr>
<h2>18. 故障排查矩阵</h2>
<table>
 <thead>
  <tr>
   <th>现象</th>
   <th>原因</th>
   <th>处理</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>本地文件不存在</td>
   <td>zip 不在 doc 目录</td>
   <td>放到 doc，路径写 <code>@/xxx.zip</code></td>
  </tr>
  <tr>
   <td>host_name 为空</td>
   <td>host_id 无效或未填</td>
   <td>检查执行主机 ID</td>
  </tr>
  <tr>
   <td>某步直接中断</td>
   <td>节点抛错</td>
   <td>看步骤明细里的 error / bt_msg</td>
  </tr>
  <tr>
   <td>database 判断错了</td>
   <td>用了 status 而非 value</td>
   <td>改用 isDatabaseExists</td>
  </tr>
  <tr>
   <td>文件「不存在」判断错</td>
   <td>用了 exists 而非 found</td>
   <td>改用 result.found</td>
  </tr>
  <tr>
   <td>Go 项目创建后找不到</td>
   <td>宝塔列表延迟</td>
   <td>用 verifyGoProjectExists 重试</td>
  </tr>
  <tr>
   <td>启动失败</td>
   <td>项目配置过期</td>
   <td>示例脚本会自动 recreate</td>
  </tr>
  <tr>
   <td>replace_config 失败</td>
   <td>yaml 里没有那个键</td>
   <td>核对 config 文件结构</td>
  </tr>
  <tr>
   <td>弹窗后没继续</td>
   <td>没点确定</td>
   <td>用户需提交；取消走 cancelled 分支</td>
  </tr>
  <tr>
   <td>脚本语法报错</td>
   <td>Monaco 红线</td>
   <td>修完再运行；可看 issue 数量</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>19. 完整配置区 + 最小可运行模板</h2>
<h3>19.1 配置区（改这里就够）</h3>
<pre><code class="language-javascript">const hostId = "";                                          // 或靠编辑器注入
const localPackagePath = "@/your-app-install.zip";
const packageFileName = "your-app-install.zip";
const binaryFileName = "your-app";
const defaultPort = 9001;
const forceUnzipAfterUpload = true;
</code></pre>
<h3>19.2 幂等设计检查清单</h3>
<ul>
 <li><input disabled type="checkbox"> 上传前 check_file_exists</li>
 <li><input disabled type="checkbox"> 上传后再 check 一次</li>
 <li><input disabled type="checkbox"> 解压前判断二进制 + force 标志</li>
 <li><input disabled type="checkbox"> 建库前 database_exists</li>
 <li><input disabled type="checkbox"> 建库后再 exists 校验</li>
 <li><input disabled type="checkbox"> 端口 port_rule_exists</li>
 <li><input disabled type="checkbox"> Go 项目 findGoProject 查重</li>
 <li><input disabled type="checkbox"> 创建后 verifyGoProjectExists</li>
 <li><input disabled type="checkbox"> 启动失败 recreate 分支</li>
</ul>
<h3>19.3 建议学习顺序</h3>
<ol>
 <li>跑通 <a href="https://bbs.ithxs.com#6-%E7%AC%AC%E4%B8%80%E4%B8%AA%E8%83%BD%E8%B7%91%E7%9A%84%E8%84%9A%E6%9C%AC">第 6 节</a> 检查文件</li>
 <li>加 <code>upload_file</code> 上传一个小 txt</li>
 <li>加 <code>request_user_input</code> 弹窗</li>
 <li>复制完整示例脚本，只改配置区</li>
 <li>对用户主机上架测试</li>
</ol>
<hr>
<h2>20. 附录：常用内置节点速查</h2>
<h3>文件</h3>
<p><code>get_dir_new</code> <code>create_dir</code> <code>create_file</code> <code>delete_file</code> <code>get_file_body</code> <code>save_file_body</code> <code>move_file</code> <code>zip_files</code> <code>unzip_files</code> <code>set_file_access</code> <code>upload_file</code> <code>check_file_exists</code> <code>replace_config_content</code></p>
<h3>数据库</h3>
<p><code>create_database</code> <code>database_exists</code> <code>delete_database</code> <code>get_database_info</code></p>
<h3>Go 项目</h3>
<p><code>get_go_project_list</code> <code>get_go_project_detail</code> <code>create_go_project</code> <code>start_go_project</code> <code>stop_go_project</code> <code>delete_go_project</code> <code>add_go_project_domain</code> <code>modify_go_project</code></p>
<h3>防火墙</h3>
<p><code>get_port_rules</code> <code>port_rule_exists</code> <code>set_port_rule</code> <code>remove_port_rule</code></p>
<h3>工具</h3>
<p><code>get_host_info</code> <code>get_user_info</code> <code>bind_user_domains_local</code> <code>request_user_input</code> <code>end_workflow</code> <code>delay</code> <code>send_email</code> <code>exec_sql</code>（仅管理端）</p>
<hr>
<h2>结语</h2>
<p>自定义工作流 = <strong>JavaScript + 宝塔 API + 一点点幂等思维</strong>。系统已提供 960 行注释完整的「一键部署小氢云脚本」作为范本：<strong>配置区改常量、工具函数原样复制、八步流程按需删减</strong>，就是你的生产脚本。</p>
<p>管理端 <strong>工作流 → 使用教程</strong> 还有可复制的短示例；遇到参数不确定，左侧 <strong>操作参考 → 点函数名看详情</strong>。</p>
<p><strong>现在就去：</strong> 新建工作流 → 粘贴第 6 节代码 → 填主机 ID → 运行。</p>]]></description><guid isPermaLink="false">/archives/post-1780274715377</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-hvPC.jpg&amp;size=m" type="image/jpeg" length="64022"/><category>小储云主机系统</category><pubDate>Mon, 1 Jun 2026 00:46:45 GMT</pubDate></item><item><title><![CDATA[小氢云商城自定义路由系统完整使用教程（含单页示例）]]></title><link>https://bbs.ithxs.com/archives/post-1780048728077</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%95%86%E5%9F%8E%E8%87%AA%E5%AE%9A%E4%B9%89%E8%B7%AF%E7%94%B1%E7%B3%BB%E7%BB%9F%E5%AE%8C%E6%95%B4%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B%EF%BC%88%E5%90%AB%E5%8D%95%E9%A1%B5%E7%A4%BA%E4%BE%8B%EF%BC%89&amp;url=/archives/post-1780048728077" width="1" height="1" alt="" style="opacity:0;">
<h1>小氢云商城自定义路由系统完整使用教程</h1>
<blockquote>
 <p>适用版本：小氢云商城系统（Go 后端 + 管理后台 web-antd）
  <br>
  阅读对象：站长、运营、二次开发者
  <br>
  最后更新：2026-05-29</p>
</blockquote>
<hr>
<h2>一、这套「路由系统」到底是什么？</h2>
<p>在小氢云商城里，<strong>自定义路由映射系统</strong>（后台菜单：<strong>路由映射管理</strong>）是一套<strong>无需改代码、无需重新打包前端</strong>就能上线新页面的能力。</p>
<p>你可以把它理解成：</p>
<pre><code>用户访问 URL  →  系统查映射表  →  读取 doc 目录文件 / 装修配置  →  渲染页面
</code></pre>
<p>典型用途包括：</p>
<ul>
 <li>落地页、活动页、品牌介绍页</li>
 <li>独立 API 文档页、帮助中心</li>
 <li>绑定独立域名做品牌站首页</li>
 <li>绑定「网站装修项目」做可视化拖拽页面</li>
 <li>付费内容页（会员专享资料、教程等）</li>
</ul>
<p><strong>和 Vue Router 的区别：</strong> PC/PE 前端里的 Vue Router 管的是商城 SPA 内部跳转；本文讲的是<strong>服务端级别的自定义路由</strong>，直接由 Go 中间件拦截 HTTP 请求并返回内容，SEO 友好、可独立访问。</p>
<hr>
<h2>二、整体架构（建议先建立心智模型）</h2>
<pre><code class="language-mermaid">flowchart TD
    A[用户请求 /promo/sale] --&gt; B{是否为 /api/* 或后台路径?}
    B --&gt;|是| C[放行给 API / 静态资源]
    B --&gt;|否| D[CustomRouteMiddleware 中间件]
    D --&gt; E{Redis 缓存命中?}
    E --&gt;|否| F[查 MySQL sky_route_mapping]
    F --&gt; G[写入 Redis 永久缓存]
    E --&gt;|是| H[读取映射配置]
    G --&gt; H
    H --&gt; I{数据源类型}
    I --&gt;|file| J[读取 doc 下 HTML/JS/CSS/PHP 等]
    I --&gt;|decoration| K[读取装修项目 JSON 配置]
    J --&gt; L{访问控制}
    K --&gt; L
    L --&gt;|需登录/付费| M[拦截并展示登录/付费页]
    L --&gt;|通过| N[占位符替换 + 静态路径重写 + 返回内容]
</code></pre>
<h3>核心存储</h3>
<table>
 <thead>
  <tr>
   <th>层级</th>
   <th>作用</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>MySQL</strong> <code>sky_route_mapping</code></td>
   <td>持久化所有路由映射配置</td>
  </tr>
  <tr>
   <td><strong>Redis</strong></td>
   <td>永久缓存映射关系，加速访问</td>
  </tr>
  <tr>
   <td><strong>doc 目录</strong></td>
   <td>与后端可执行文件同级的文件仓库，存放 HTML/静态资源/PHP 等</td>
  </tr>
 </tbody>
</table>
<h3>缓存键规则</h3>
<pre><code>键名 = route_mapping: + md5(全局配置 zhuzhan + 路由路径)
值   = 文件相对路径，如 promo/sale.html
</code></pre>
<p>创建、更新、删除映射时系统会自动维护 Redis；也可在后台点击 <strong>「刷新缓存」</strong> 批量同步。</p>
<hr>
<h2>三、功能清单（一张表看懂能做什么）</h2>
<table>
 <thead>
  <tr>
   <th>功能</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>路由映射</strong></td>
   <td>如 <code>/about</code> → <code>pages/about.html</code></td>
  </tr>
  <tr>
   <td><strong>域名映射</strong></td>
   <td>如 <code>brand.example.com</code> 根路径 → 指定页面（需 DNS 解析到服务器）</td>
  </tr>
  <tr>
   <td><strong>数据源：文件</strong></td>
   <td>HTML / JSON / CSS / JS / PHP / SVG / MD 等文本格式</td>
  </tr>
  <tr>
   <td><strong>数据源：装修项目</strong></td>
   <td>绑定后台「网站装修」拖拽项目，前端 SPA 动态渲染</td>
  </tr>
  <tr>
   <td><strong>设备类型</strong></td>
   <td><code>pc</code> 电脑端 / <code>pe</code> 手机端 / <code>def</code> 通用</td>
  </tr>
  <tr>
   <td><strong>强制登录</strong></td>
   <td>未登录用户看到内置登录页（需开启用户系统）</td>
  </tr>
  <tr>
   <td><strong>付费访问</strong></td>
   <td>设置价格，未购买用户看到付费引导页</td>
  </tr>
  <tr>
   <td><strong>元数据 Metadata</strong></td>
   <td>JSON 表单配置，写入 Cookie，页面内可用 <code>[元数据]</code> 占位符读取</td>
  </tr>
  <tr>
   <td><strong>占位符变量</strong></td>
   <td>自动替换网站标题、备案号、Logo、SEO 等系统配置</td>
  </tr>
  <tr>
   <td><strong>静态资源自动映射</strong></td>
   <td>页面同级目录下的 <code>assets/</code>、<code>static/</code>、<code>css/</code> 等自动可访问</td>
  </tr>
  <tr>
   <td><strong>导入/导出</strong></td>
   <td>批量迁移路由配置</td>
  </tr>
  <tr>
   <td><strong>在线文件管理</strong></td>
   <td>后台可直接上传、编辑 doc 目录文件</td>
  </tr>
  <tr>
   <td><strong>访问统计</strong></td>
   <td>今日/累计访问量（Redis 统计）</td>
  </tr>
  <tr>
   <td><strong>PHP 动态页</strong></td>
   <td>配置 PHP 解释器后可执行 <code>.php</code> 文件</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>四、快速上手：5 分钟发布第一个单页</h2>
<h3>步骤 1：进入管理后台</h3>
<p>路径：<strong>系统管理 → 路由映射管理</strong>（或全局搜索「路由映射」）。</p>
<h3>步骤 2：准备页面文件</h3>
<p><strong>方式 A — 后台在线创建（推荐新手）</strong></p>
<ol>
 <li>点击 <strong>「浏览文件」</strong></li>
 <li>在 <code>doc</code> 目录下新建文件夹，如 <code>pages</code></li>
 <li>新建文件 <code>welcome.html</code>，粘贴下方【完整单页示例】代码</li>
 <li>保存</li>
</ol>
<p><strong>方式 B — 服务器手动上传</strong></p>
<pre><code class="language-bash"># 在后端程序同级目录
mkdir -p doc/pages
cp welcome.html doc/pages/welcome.html
</code></pre>
<blockquote>
 <p><code>doc</code> 目录若不存在，系统会在首次浏览文件时自动创建。</p>
</blockquote>
<h3>步骤 3：添加路由映射</h3>
<p>点击 <strong>「添加路由」</strong>，填写：</p>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>示例值</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>映射方式</td>
   <td>通过路由</td>
  </tr>
  <tr>
   <td>路由路径</td>
   <td><code>/welcome</code></td>
  </tr>
  <tr>
   <td>数据源类型</td>
   <td>文件</td>
  </tr>
  <tr>
   <td>文件路径</td>
   <td><code>pages/welcome.html</code></td>
  </tr>
  <tr>
   <td>状态</td>
   <td>启用</td>
  </tr>
  <tr>
   <td>设备类型</td>
   <td>通用</td>
  </tr>
 </tbody>
</table>
<p>保存后，访问：</p>
<pre><code>https://你的域名/welcome
</code></pre>
<p>即可看到页面。</p>
<h3>步骤 4：验证</h3>
<ul>
 <li>页面标题、Logo 等占位符是否被正确替换</li>
 <li>打开浏览器开发者工具 → Network，确认 CSS/JS 加载 200</li>
 <li>后台列表中「访问统计」是否增加</li>
</ul>
<hr>
<h2>五、完整单页 HTML 示例（可直接复制使用）</h2>
<p>将以下代码保存为 <code>doc/pages/welcome.html</code>，并映射路由 <code>/welcome</code>：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8" /&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
  &lt;title&gt;[title] - 欢迎页&lt;/title&gt;
  &lt;meta name="keywords" content="[seo_keywords]" /&gt;
  &lt;meta name="description" content="[seo_description]" /&gt;
  &lt;link rel="icon" href="[favicon]" /&gt;
  &lt;style&gt;
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
    }
    .card {
      background: rgba(255,255,255,0.95);
      color: #333;
      border-radius: 16px;
      padding: 48px 40px;
      max-width: 520px;
      width: 90%;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
      text-align: center;
    }
    .logo { width: 64px; height: 64px; border-radius: 12px; margin-bottom: 20px; }
    h1 { font-size: 28px; margin-bottom: 12px; }
    p { color: #666; line-height: 1.8; margin-bottom: 24px; }
    .meta { font-size: 13px; color: #999; margin-bottom: 32px; }
    .btn {
      display: inline-block;
      padding: 12px 32px;
      background: #667eea;
      color: #fff;
      text-decoration: none;
      border-radius: 8px;
      font-weight: 600;
      transition: transform 0.2s, box-shadow 0.2s;
    }
    .btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(102,126,234,0.4); }
    footer { margin-top: 32px; font-size: 12px; color: #bbb; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="card"&gt;
    &lt;img class="logo" src="[logo]" alt="Logo" onerror="this.style.display='none'" /&gt;
    &lt;h1&gt;欢迎来到 [title]&lt;/h1&gt;
    &lt;p&gt;这是通过&lt;strong&gt;自定义路由系统&lt;/strong&gt;发布的独立单页。&lt;br/&gt;无需修改前端代码，后台配置即可上线。&lt;/p&gt;
    &lt;div class="meta"&gt;
      当前路由：&lt;code&gt;[router]&lt;/code&gt;&lt;br/&gt;
      站点地址：&lt;a href="[base_url]"&gt;[base_url]&lt;/a&gt;&lt;br/&gt;
      建站时间：[site_time]
    &lt;/div&gt;
    &lt;a class="btn" href="[base_url]"&gt;进入商城首页&lt;/a&gt;
    &lt;footer&gt;
      [RecordNumber] [PoliceRecordNumber]
    &lt;/footer&gt;
  &lt;/div&gt;

  &lt;script&gt;
    // 元数据占位符已被 Unicode 转码，可安全嵌入
    var routeMeta = "[元数据]";
    if (routeMeta &amp;&amp; routeMeta !== "[元数据]") {
      try {
        var meta = JSON.parse(routeMeta);
        console.log('路由元数据:', meta);
      } catch (e) {}
    }
    console.log('自定义路由页面加载成功，路径:', '[router]');
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3>这个示例演示了什么？</h3>
<ol>
 <li><strong>占位符自动替换</strong>：<code>[title]</code>、<code>[logo]</code>、<code>[base_url]</code> 等来自系统配置</li>
 <li><strong>纯 CSS 单页</strong>：无外部依赖，复制即用</li>
 <li><strong>元数据读取</strong>：若在后台配置了 Metadata，可通过 <code>[元数据]</code> 在 JS 中使用</li>
 <li><strong>备案信息</strong>：<code>[RecordNumber]</code>、<code>[PoliceRecordNumber]</code> 自动填充</li>
</ol>
<hr>
<h2>六、占位符（变量）完整列表</h2>
<p>在 <strong>所有文本格式文件</strong>（HTML、JS、CSS、JSON、TXT、PHP 等）中，以下占位符会在访问时自动替换：</p>
<table>
 <thead>
  <tr>
   <th>占位符</th>
   <th>替换内容</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>[title]</code> / <code>[Title]</code> / <code>[网站标题]</code></td>
   <td>网站标题</td>
  </tr>
  <tr>
   <td><code>[base_url]</code></td>
   <td>系统网址（优先 config.yaml，否则从请求构建）</td>
  </tr>
  <tr>
   <td><code>[RecordNumber]</code> / <code>[备案号]</code></td>
   <td>ICP 备案号</td>
  </tr>
  <tr>
   <td><code>[PoliceRecordNumber]</code> / <code>[公安备案号]</code></td>
   <td>公安备案号</td>
  </tr>
  <tr>
   <td><code>[logo]</code> / <code>[favicon]</code></td>
   <td>网站 Logo / 图标</td>
  </tr>
  <tr>
   <td><code>[seo_keywords]</code> / <code>[SEO关键词]</code></td>
   <td>SEO 关键词</td>
  </tr>
  <tr>
   <td><code>[seo_description]</code> / <code>[SEO描述]</code></td>
   <td>SEO 描述</td>
  </tr>
  <tr>
   <td><code>[site_time]</code> / <code>[建站时间]</code></td>
   <td>建站时间</td>
  </tr>
  <tr>
   <td><code>[pcurl]</code> / <code>[PC背景]</code></td>
   <td>PC 端背景图 URL</td>
  </tr>
  <tr>
   <td><code>[peurl]</code> / <code>[手机背景]</code></td>
   <td>手机端背景图 URL</td>
  </tr>
  <tr>
   <td><code>[router]</code> / <code>[ROUTER]</code></td>
   <td><strong>当前访问的路由路径</strong></td>
  </tr>
  <tr>
   <td><code>[元数据]</code> / <code>[metadata]</code></td>
   <td>当前路由 Metadata JSON（Unicode 转码，可安全嵌入 script）</td>
  </tr>
 </tbody>
</table>
<blockquote>
 <p><strong>注意</strong>：二进制文件（图片等）不会做占位符替换，避免损坏文件。</p>
</blockquote>
<hr>
<h2>七、静态资源：为什么你的 CSS/JS 能自动加载？</h2>
<p>假设你映射了 <code>/landing</code> → <code>campaign/index.html</code>，且文件结构为：</p>
<pre><code>doc/
└── campaign/
    ├── index.html
    ├── assets/
    │   ├── app.js
    │   └── style.css
    └── static/
        └── banner.png
</code></pre>
<p>当 <code>index.html</code> 中引用：</p>
<pre><code class="language-html">&lt;link rel="stylesheet" href="/assets/style.css" /&gt;
&lt;script src="/assets/app.js"&gt;&lt;/script&gt;
</code></pre>
<p>系统会<strong>自动重写路径</strong>为：</p>
<pre><code>/landing/assets/style.css
/landing/assets/app.js
</code></pre>
<p>并从 <code>doc/campaign/assets/</code> 目录读取文件返回。</p>
<h3>支持的静态目录前缀</h3>
<p><code>/assets/</code>、<code>/static/</code>、<code>/js/</code>、<code>/css/</code>、<code>/images/</code>、<code>/img/</code>、<code>/fonts/</code>、<code>/libs/</code>、<code>/lib/</code> 等。</p>
<h3>最佳实践</h3>
<ul>
 <li>把静态资源放在 HTML <strong>同级或子目录</strong>，不要散落到 doc 根目录</li>
 <li>打包 UniApp/H5 项目时，直接解压到 <code>doc/某路由名/</code> 下即可</li>
 <li>路径写 <code>/assets/xxx</code> 即可，系统会自动加路由前缀</li>
</ul>
<hr>
<h2>八、进阶用法</h2>
<h3>8.1 域名映射（独立品牌站）</h3>
<p><strong>场景</strong>：<code>shop.example.com</code> 是商城，<code>brand.example.com</code> 想展示品牌首页。</p>
<p><strong>配置步骤</strong>：</p>
<ol>
 <li>DNS 将 <code>brand.example.com</code> A 记录解析到服务器 IP</li>
 <li>后台添加映射：
  <ul>
   <li>映射方式：<strong>通过域名</strong></li>
   <li>域名：<code>brand.example.com</code></li>
   <li>文件路径：<code>brand/home.html</code></li>
  </ul></li>
 <li>用户访问 <code>https://brand.example.com/</code> 时渲染该页面</li>
</ol>
<blockquote>
 <p><code>/api/*</code>、<code>/static/*</code> 等非根路径仍正常走后端，不影响接口。</p>
</blockquote>
<h3>8.2 绑定网站装修项目</h3>
<p><strong>场景</strong>：已在「网站装修」里拖拽好了 PC/手机页面，想挂到自定义 URL。</p>
<ol>
 <li>数据源类型选 <strong>「网站装修项目」</strong></li>
 <li>选择对应装修项目 ID</li>
 <li>保存后，PC 端 SPA 的 <code>DynamicRoute.vue</code> 会读取装修 JSON 并渲染组件树</li>
</ol>
<blockquote>
 <p>装修类型不走直接 HTML 返回，而是注入到前端 SPA 的 sessionStorage，由 Vue 组件渲染。</p>
</blockquote>
<h3>8.3 PHP 动态页面</h3>
<ol>
 <li>在 <strong>网站设置 → 基础设置</strong> 配置 PHP 解释器路径</li>
 <li>上传 <code>doc/api/hello.php</code></li>
 <li>映射 <code>/api-demo</code> → <code>api/hello.php</code></li>
</ol>
<p>系统会用 CLI 模式执行 PHP，并传递 HTTP 上下文（Method、Headers、Body 等）。</p>
<p><strong>安全限制</strong>：<code>exec</code>、<code>eval</code>、<code>shell_exec</code>、<code>system</code> 等高危函数默认禁用。</p>
<h3>8.4 强制登录 &amp; 付费访问</h3>
<table>
 <thead>
  <tr>
   <th>设置</th>
   <th>行为</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>强制登录 = 开启</td>
   <td>未登录用户看到内置登录页</td>
  </tr>
  <tr>
   <td>付费 + 价格</td>
   <td>需先登录，再检查是否已购买该路由</td>
  </tr>
 </tbody>
</table>
<blockquote>
 <p>若全局关闭了用户系统（<code>user_code = false</code>），强制登录和付费功能不会生效。</p>
</blockquote>
<h3>8.5 元数据 Metadata（动态表单）</h3>
<p>Metadata 是一个 JSON 数组，描述表单组件（input、select、radio 等）。</p>
<p><strong>后台操作</strong>：添加路由 → 元数据 → <strong>「可视化配置」</strong>。</p>
<p><strong>页面中使用</strong>：</p>
<pre><code class="language-html">&lt;script&gt;
  var fields = JSON.parse("[元数据]");
  // fields 是表单组件数组，可动态渲染表单
&lt;/script&gt;
</code></pre>
<p>系统还会将 Metadata 写入 Cookie（键名 <code>route_meta_</code> + md5(域名+路由)），有效期 30 天，供跨页面读取。</p>
<h3>8.6 设备类型分流</h3>
<p>同一 URL，可为 PC 和手机配置<strong>不同映射</strong>：</p>
<ul>
 <li><code>/promo</code> + 设备类型 <code>pc</code> → <code>promo/pc.html</code></li>
 <li><code>/promo</code> + 设备类型 <code>pe</code> → <code>promo/mobile.html</code></li>
</ul>
<p>系统通过 User-Agent 自动识别设备；调试时可加 <code>?force_device=pc</code> 或 <code>?force_device=pe</code> 强制指定。</p>
<hr>
<h2>九、后台文件管理 API（开发者参考）</h2>
<p>除可视化「浏览文件」外，还提供完整文件操作接口（均需管理员 Token）：</p>
<table>
 <thead>
  <tr>
   <th>接口</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>GET /api/admin/route-mapping/doc-files?path=xxx</code></td>
   <td>列出 doc 目录</td>
  </tr>
  <tr>
   <td><code>POST /api/admin/route-mapping/upload</code></td>
   <td>上传文件</td>
  </tr>
  <tr>
   <td><code>POST /api/admin/route-mapping/create-file</code></td>
   <td>创建文件</td>
  </tr>
  <tr>
   <td><code>POST /api/admin/route-mapping/update-file</code></td>
   <td>更新文件内容</td>
  </tr>
  <tr>
   <td><code>GET /api/admin/route-mapping/read-file</code></td>
   <td>读取文件</td>
  </tr>
  <tr>
   <td><code>POST /api/admin/route-mapping/unzip-file</code></td>
   <td>解压 zip（适合整站 H5 包）</td>
  </tr>
 </tbody>
</table>
<h3>创建映射示例（curl）</h3>
<pre><code class="language-bash">TOKEN="你的管理员Token"

curl -X POST http://127.0.0.1:8080/api/admin/route-mapping/create \
  -H "Content-Type: application/json" \
  -H "admin-token: Bearer $TOKEN" \
  -d '{
    "route": "/welcome",
    "route_type": "route",
    "file_path": "pages/welcome.html",
    "source_type": "file",
    "title": "欢迎页",
    "status": 1,
    "device_type": "def",
    "force_login": 0,
    "is_paid": 0
  }'
</code></pre>
<hr>
<h2>十、导入 / 导出（批量迁移）</h2>
<p><strong>导出</strong>：勾选路由 → 点击「导出」→ 得到 JSON 配置文件。</p>
<p><strong>导入规则</strong>：</p>
<ul>
 <li>按「路由路径 + 设备类型」判重</li>
 <li>已存在则更新，不存在则新建</li>
 <li>支持 JSON 文件或远程 URL 导入</li>
</ul>
<p>适合从测试环境迁移到生产环境，或备份路由配置。</p>
<hr>
<h2>十一、请求处理优先级（排查问题时必看）</h2>
<p>中间件 <code>CustomRouteMiddleware</code> 的执行顺序：</p>
<ol>
 <li><strong>跳过</strong> <code>/api/*</code>、管理后台路径、砍价分享页</li>
 <li><strong>静态资源路径</strong> → 尝试从 doc 目录匹配</li>
 <li><strong>根路径 <code>/</code></strong> → 优先尝试<strong>域名映射</strong></li>
 <li><strong>按路由路径</strong>精确匹配（<code>/promo</code> 与 <code>/promo/</code> 等价）</li>
 <li><strong>父路由静态资源</strong> → 如 <code>/landing/css/style.css</code> 找 <code>/landing</code> 映射</li>
 <li>未命中 → 交给 SPA 的 NoRoute 处理（404 或装修兜底）</li>
</ol>
<h3>访问控制优先级</h3>
<ol>
 <li>全局强制登录中间件</li>
 <li>路由级强制登录</li>
 <li>路由级付费访问</li>
</ol>
<hr>
<h2>十二、常见问题 FAQ</h2>
<h3>Q1：访问自定义路由返回 404？</h3>
<p>检查清单：</p>
<ul>
 <li><input disabled type="checkbox"> 路由映射状态是否为 <strong>启用</strong></li>
 <li><input disabled type="checkbox"> 文件路径是否正确（相对于 doc，不含 <code>doc/</code> 前缀）</li>
 <li><input disabled type="checkbox"> doc 目录下文件是否真实存在</li>
 <li><input disabled type="checkbox"> 设备类型是否匹配（PC 页设置了 <code>pe</code> -only 时，电脑访问会 404）</li>
 <li><input disabled type="checkbox"> 点击「刷新缓存」后重试</li>
</ul>
<h3>Q2：改了 HTML 但页面没变化？</h3>
<ul>
 <li>文件内容<strong>不走 Redis 缓存</strong>，改完应即时生效</li>
 <li>若仍不变，检查浏览器缓存（Ctrl+F5 / Cmd+Shift+R）</li>
 <li>若改了<strong>映射配置</strong>本身，需刷新路由缓存</li>
</ul>
<h3>Q3：CSS/JS 404？</h3>
<ul>
 <li>确认静态资源在 HTML 同级目录的 <code>assets/</code> 或 <code>static/</code> 下</li>
 <li>HTML 里用 <code>/assets/xxx</code> 相对根路径写法，系统会自动加路由前缀</li>
 <li>不要手动写完整路由前缀（除非你知道自己在做什么）</li>
</ul>
<h3>Q4：强制登录设置了但不生效？</h3>
<ul>
 <li>检查 <strong>用户系统</strong> 是否开启（网站设置 → 用户系统）</li>
 <li>用户系统关闭时，后台会显示「无效」标签</li>
</ul>
<h3>Q5：能和商城 Vue 路由冲突吗？</h3>
<ul>
 <li>自定义路由优先级<strong>低于</strong> <code>/api/*</code> 和后台路径</li>
 <li>与 PC SPA 路由：若后端中间件已命中并返回内容，则不会进入 Vue；未命中则走 SPA 的 <code>DynamicRoute.vue</code> 兜底</li>
 <li><strong>建议</strong>：自定义路由使用 <code>/promo/</code>、<code>/landing/</code> 等独立前缀，避免与商城内置路径冲突</li>
</ul>
<h3>Q6：doc 目录在哪？</h3>
<p>与后端<strong>可执行程序同级</strong>，例如：</p>
<pre><code>/opt/xqy/
├── server          # 后端程序
├── doc/            # ← 这里
│   └── pages/
│       └── welcome.html
└── static/
</code></pre>
<hr>
<h2>十三、推荐目录结构</h2>
<pre><code> doc/
 ├── pages/           # 通用单页
 │   ├── about.html
 │   └── contact.html
 ├── campaign/        # 活动页（含静态资源子目录）
 │   ├── index.html
 │   └── assets/
 ├── docs/            # 文档
 │   └── api.html
 └── brand/           # 域名映射专用
     └── home.html
</code></pre>
<hr>
<h2>十四、安全注意事项</h2>
<ol>
 <li><strong>禁止路径遍历</strong>：文件路径不允许 <code>..</code>，无法访问 doc 外文件</li>
 <li><strong>管理接口鉴权</strong>：所有配置/文件操作需管理员 Token</li>
 <li><strong>PHP 高危函数禁用</strong>：生产环境务必配置解释器并审查 PHP 代码</li>
 <li><strong>付费页</strong>：涉及资金，修改价格前确认已购用户权益策略</li>
 <li><strong>唯一路由</strong>：同一路由 + 设备类型不可重复，防止映射冲突</li>
</ol>
<hr>
<h2>十五、总结</h2>
<table>
 <thead>
  <tr>
   <th>你想做什么</th>
   <th>怎么做</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>快速上一个介绍页</td>
   <td>doc 放 HTML → 后台添加路由映射 → 访问 URL</td>
  </tr>
  <tr>
   <td>上线 H5 活动整包</td>
   <td>解压到 doc/xxx/ → 映射 /xxx → 静态资源自动可用</td>
  </tr>
  <tr>
   <td>独立域名首页</td>
   <td>域名映射 + DNS 解析</td>
  </tr>
  <tr>
   <td>可视化拖拽页</td>
   <td>数据源选「装修项目」</td>
  </tr>
  <tr>
   <td>会员专享内容</td>
   <td>开启付费 + 设价格</td>
  </tr>
  <tr>
   <td>动态接口页</td>
   <td>上传 PHP + 配置解释器</td>
  </tr>
 </tbody>
</table>
<p>自定义路由系统是小氢云商城<strong>最灵活的页面发布通道</strong>——运营同学可以独立上线页面，开发同学可以对接 PHP/H5 整包，无需每次改代码发版。</p>
<hr>
<h2>附录：数据库表字段速查</h2>
<p><code>sky_route_mapping</code> 主要字段：</p>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>含义</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>route</td>
   <td>路由路径或域名</td>
  </tr>
  <tr>
   <td>route_type</td>
   <td><code>route</code> 或 <code>domain</code></td>
  </tr>
  <tr>
   <td>file_path</td>
   <td>doc 下相对路径</td>
  </tr>
  <tr>
   <td>source_type</td>
   <td><code>file</code> 或 <code>decoration</code></td>
  </tr>
  <tr>
   <td>decoration_id</td>
   <td>装修项目 ID</td>
  </tr>
  <tr>
   <td>force_login</td>
   <td>是否强制登录</td>
  </tr>
  <tr>
   <td>device_type</td>
   <td>pc / pe / def</td>
  </tr>
  <tr>
   <td>is_paid / price</td>
   <td>付费设置</td>
  </tr>
  <tr>
   <td>metadata</td>
   <td>元数据 JSON</td>
  </tr>
  <tr>
   <td>status</td>
   <td>1 启用 / 0 禁用</td>
  </tr>
 </tbody>
</table>
<hr>
<p><em>本文由小氢云商城技术团队整理，基于系统源码与后台「路由映射管理」模块编写。如有疑问，欢迎在评论区留言。</em></p>]]></description><guid isPermaLink="false">/archives/post-1780048728077</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-HOph.jpg&amp;size=m" type="image/jpeg" length="79460"/><category>小氢云商城系统</category><pubDate>Fri, 29 May 2026 09:58:50 GMT</pubDate></item><item><title><![CDATA[小氢云商城模板开发完全指南：接口调用、目录规范与提审清单]]></title><link>https://bbs.ithxs.com/archives/post-1780044669502</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%95%86%E5%9F%8E%E6%A8%A1%E6%9D%BF%E5%BC%80%E5%8F%91%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E3%80%81%E7%9B%AE%E5%BD%95%E8%A7%84%E8%8C%83%E4%B8%8E%E6%8F%90%E5%AE%A1%E6%B8%85%E5%8D%95&amp;url=/archives/post-1780044669502" width="1" height="1" alt="" style="opacity:0;">
<h1>小氢云商城模板开发完全指南</h1>
<blockquote>
 <p>本文面向<strong>第三方模板开发者</strong>与<strong>站点二次开发者</strong>，系统梳理小氢云商城（Go 架构）下的模板开发规范、接口调用方式与上架流程。读完本文，你应该能独立完成一个可安装的静态模板，并正确对接商城核心业务接口。</p>
</blockquote>
<hr>
<h2>一、先搞清楚：模板是什么</h2>
<p>在小氢云商城里，「模板」指的是<strong>替换或扩展前台展示层</strong>的前端工程。用户访问商城时，系统通过<strong>路由映射</strong>把 URL 指向你打包好的 <code>index.html</code>，你的页面再通过 REST API 与后端通信，完成商品展示、下单、支付、查单等完整链路。</p>
<p>当前系统支持两类模板形态：</p>
<table>
 <thead>
  <tr>
   <th>形态</th>
   <th>技术栈</th>
   <th>适用场景</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>静态 SPA 模板</strong>（推荐）</td>
   <td>Vue / React / UniApp H5 打包产物</td>
   <td>现代商城前台、高度自定义 UI</td>
  </tr>
  <tr>
   <td><strong>经典 PHP 模板</strong>（兼容晴玖规范）</td>
   <td>PHP + HTML + <code>route.php</code></td>
   <td>轻量改造、沿用旧版模板生态</td>
  </tr>
 </tbody>
</table>
<blockquote>
 <p>小氢云商城后端为 <strong>Go + Gin</strong>，接口统一走 <code>/api/*</code>。PHP 模板中的 <code>/?mod=route&amp;p=Goods</code> 等旧式路由，在新系统中应改为<strong>前端路由 + REST API</strong> 的方式实现。</p>
</blockquote>
<hr>
<h2>二、模板目录结构规范</h2>
<h3>2.1 静态 SPA 模板（主流方式）</h3>
<p>从<strong>模板商店</strong>安装后，系统会将 zip 包解压到 <code>doc/{template_slug}/</code> 目录，并自动创建路由映射。</p>
<p><strong>标准目录结构：</strong></p>
<pre><code>my-template/
├── index.html          # 入口文件（必需，文件名必须为 index）
├── index.png           # 预览图（建议 800×600px，上架用）
├── assets/             # JS / CSS 打包产物（UniApp / Vite 默认目录）
│   ├── index-xxx.js
│   └── index-xxx.css
├── static/             # 图片、字体等静态资源（可选）
│   └── images/
└── conf.json           # 模板配置（可选，见下文）
</code></pre>
<p><strong>硬性要求：</strong></p>
<ol>
 <li>包内必须存在名为 <strong><code>index</code></strong> 的首页文件（<code>index.html</code> / <code>index.htm</code> / <code>index.php</code>，优先 <code>.html</code>）</li>
 <li><code>template_slug</code> 在授权站配置，<strong>仅允许英文和数字</strong>，全局唯一</li>
 <li>静态资源与 <code>index.html</code> <strong>放在同一目录树下</strong>，便于路由中间件自动解析</li>
</ol>
<h3>2.2 经典 PHP 模板（晴玖兼容）</h3>
<p>若你沿用晴玖商城模板规范，目录如下：</p>
<pre><code>template/{应用标识}/
├── index.php         # 模板入口
├── index.png         # 预览图
├── route.php         # 路由跳转（旧式页面导航）
├── conf.json         # 模板配置
└── help.html         # 帮助文档（有扩展功能时建议提供）
</code></pre>
<p><code>route.php</code> 示例（旧式跳转，新开发建议用前端路由替代）：</p>
<pre><code class="language-php">&lt;?php
switch ($_GET['p']) {
    case 'Goods':
        header("Location: " . ROOT_DIR . "#/pages/shop/shop?gid=" . $_GET['gid']);
        break;
    case 'Order':
        header("Location: " . ROOT_DIR . "#/pages/order/order");
        break;
    default:
        header("Location: " . ROOT_DIR);
        break;
}
</code></pre>
<hr>
<h2>三、模板配置文件 conf.json</h2>
<p><code>conf.json</code> 让站长在后台可视化修改模板参数，无需改代码。</p>
<pre><code class="language-json">{
    "name": "极简商城模板",
    "version": "1.0.0",
    "type": "1",
    "content": "一款简洁现代的商城前台模板",
    "extend": [
        {
            "type": 1,
            "name": "主题色",
            "value": "#1890ff",
            "Tips": "全局主色调，支持 HEX 色值"
        },
        {
            "type": 2,
            "name": "首页布局",
            "value": "1",
            "data": {
                "1": "单列瀑布流",
                "2": "双列网格",
                "3": "分类优先"
            }
        }
    ]
}
</code></pre>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>name</code></td>
   <td>模板名称</td>
  </tr>
  <tr>
   <td><code>version</code></td>
   <td>版本号</td>
  </tr>
  <tr>
   <td><code>type</code></td>
   <td>模板类型标识</td>
  </tr>
  <tr>
   <td><code>content</code></td>
   <td>模板描述</td>
  </tr>
  <tr>
   <td><code>extend[].type=1</code></td>
   <td>文本输入框</td>
  </tr>
  <tr>
   <td><code>extend[].type=2</code></td>
   <td>下拉选择框，需配 <code>data</code> 选项</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>四、接口调用规范</h2>
<h3>4.1 基础约定</h3>
<table>
 <thead>
  <tr>
   <th>项目</th>
   <th>规范</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>基础路径</td>
   <td><code>{站点域名}/api</code></td>
  </tr>
  <tr>
   <td>请求格式</td>
   <td><code>Content-Type: application/json</code></td>
  </tr>
  <tr>
   <td>成功标识</td>
   <td><code>code === 200</code>（部分旧接口兼容 <code>code === 1</code>）</td>
  </tr>
  <tr>
   <td>失败标识</td>
   <td><code>code === -1</code>，错误信息在 <code>msg</code> 字段</td>
  </tr>
  <tr>
   <td>登录认证</td>
   <td>请求头 <code>user-token: Bearer {token}</code></td>
  </tr>
 </tbody>
</table>
<p><strong>统一响应结构：</strong></p>
<pre><code class="language-json">{
  "code": 200,
  "msg": "操作成功",
  "data": { ... }
}
</code></pre>
<h3>4.2 获取站点基础信息（模板初始化必调）</h3>
<p>模板加载后，<strong>第一件事</strong>应拉取站点配置，用于渲染 Logo、公告、默认商品图、登录状态等。</p>
<pre><code class="language-javascript">// 获取网站基础信息（含 sitename、notice、def_img、User 等）
const res = await fetch('/api/inform', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});
const { code, data } = await res.json();

// 获取前端公共配置
const config = await fetch('/api/getConfig', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});
</code></pre>
<blockquote>
 <p><code>/api/inform</code> 支持<strong>可选认证</strong>：携带 <code>user-token</code> 时返回当前用户信息；不传则为游客模式。</p>
</blockquote>
<h3>4.3 用户登录与 Token 管理</h3>
<p><strong>登录：</strong></p>
<pre><code class="language-javascript">const res = await fetch('/api/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: 'demo',
    password: '123456',
    code: ''        // 验证码（若后台开启）
  })
});
const { code, data } = await res.json();
if (code === 200) {
  localStorage.setItem('user-token', data.token);
}
</code></pre>
<p><strong>携带 Token 发起请求：</strong></p>
<pre><code class="language-javascript">const token = localStorage.getItem('user-token');

const res = await fetch('/api/getUserInfo', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'user-token': `Bearer ${token}`
  }
});
</code></pre>
<p><strong>Token 无感刷新：</strong></p>
<p>当接口返回 <code>code === 401</code> 时，调用 <code>POST /api/refresh-token</code>（同样携带 <code>user-token</code> 头），拿到新 token 后重试原请求。</p>
<h3>4.4 封装通用请求函数（推荐）</h3>
<pre><code class="language-javascript">const API_BASE = window.location.origin;

async function api(url, params = {}, method = 'POST') {
  const token = localStorage.getItem('user-token');
  const headers = { 'Content-Type': 'application/json' };
  if (token) headers['user-token'] = `Bearer ${token.trim()}`;

  const options = { method, headers };
  if (method !== 'GET') options.body = JSON.stringify(params);

  const res = await fetch(API_BASE + url, options);
  const data = await res.json();

  if (data.code === 401) {
    // 触发登录弹窗或跳转登录页
    throw new Error('登录已过期');
  }
  if (data.code !== 200 &amp;&amp; data.code !== 1) {
    throw new Error(data.msg || '请求失败');
  }
  return data;
}

// 使用示例
const goods = await api('/api/getGoods', { cid: 1, page: 1, limit: 20 });
const detail = await api('/api/getGoodsById', { id: 123 });
</code></pre>
<h3>4.5 核心业务接口速查</h3>
<h4>商品与分类</h4>
<table>
 <thead>
  <tr>
   <th>接口</th>
   <th>方法</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>/api/class</code></td>
   <td>Any</td>
   <td>获取商品分类</td>
  </tr>
  <tr>
   <td><code>/api/getGoods</code></td>
   <td>Any</td>
   <td>分页商品列表（<code>cid</code>, <code>page</code>, <code>limit</code>, <code>keyword</code>）</td>
  </tr>
  <tr>
   <td><code>/api/getGoodsById</code></td>
   <td>Any</td>
   <td>商品详情（<code>id</code>）</td>
  </tr>
  <tr>
   <td><code>/api/searchCourse</code></td>
   <td>Any</td>
   <td>搜索商品</td>
  </tr>
  <tr>
   <td><code>/api/review/list</code></td>
   <td>Any</td>
   <td>商品评价列表</td>
  </tr>
 </tbody>
</table>
<h4>下单与订单</h4>
<table>
 <thead>
  <tr>
   <th>接口</th>
   <th>方法</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>/api/add</code></td>
   <td>Any</td>
   <td>创建订单</td>
  </tr>
  <tr>
   <td><code>/api/OrderPrice</code></td>
   <td>Any</td>
   <td>计算订单价格</td>
  </tr>
  <tr>
   <td><code>/api/PreviewPrice</code></td>
   <td>Any</td>
   <td>价格预览（含优惠券等）</td>
  </tr>
  <tr>
   <td><code>/api/queryOrder</code></td>
   <td>Any</td>
   <td>查询订单</td>
  </tr>
  <tr>
   <td><code>/api/getOrderDetail</code></td>
   <td>Any</td>
   <td>订单详情</td>
  </tr>
  <tr>
   <td><code>/api/orderByKey</code></td>
   <td>Any</td>
   <td>游客查单（按 key）</td>
  </tr>
 </tbody>
</table>
<h4>支付</h4>
<table>
 <thead>
  <tr>
   <th>接口</th>
   <th>方法</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>/api/PaymentWay</code></td>
   <td>Any</td>
   <td>获取可用支付方式</td>
  </tr>
  <tr>
   <td><code>/api/pay</code></td>
   <td>GET</td>
   <td>统一支付入口</td>
  </tr>
 </tbody>
</table>
<h4>用户中心</h4>
<table>
 <thead>
  <tr>
   <th>接口</th>
   <th>方法</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>/api/UserData</code></td>
   <td>Any</td>
   <td>用户数据概览</td>
  </tr>
  <tr>
   <td><code>/api/getUserInfo</code></td>
   <td>Any</td>
   <td>当前用户信息</td>
  </tr>
  <tr>
   <td><code>/api/coupon/my</code></td>
   <td>Any</td>
   <td>我的优惠券</td>
  </tr>
  <tr>
   <td><code>/api/cart/list</code></td>
   <td>GET</td>
   <td>购物车列表</td>
  </tr>
 </tbody>
</table>
<blockquote>
 <p>完整接口清单见项目 <code>doc/api/client/</code> 目录，按模块分为商品、订单、支付、认证等文档。</p>
</blockquote>
<h3>4.6 图片地址处理</h3>
<ul>
 <li>本地上传图片路径形如 <code>/static/img/xxx.png</code>，<strong>直接拼接站点域名</strong>即可，无需走代理</li>
 <li>相对路径统一用 <code>window.location.origin</code> 拼接，避免硬编码域名</li>
 <li>商品默认图优先取 <code>/api/inform</code> 返回的 <code>def_img</code> 字段</li>
</ul>
<pre><code class="language-javascript">function resolveImage(url) {
  if (!url) return '';
  if (url.startsWith('http')) return url;
  return window.location.origin + (url.startsWith('/') ? url : '/' + url);
}
</code></pre>
<hr>
<h2>五、路由映射与静态资源（关键！）</h2>
<p>模板安装后，系统会在<strong>路由映射</strong>中创建一条记录（默认路由 <code>/</code>，<strong>初始状态为关闭</strong>，需站长手动启用）。</p>
<h3>5.1 子路由下的静态资源自动重写</h3>
<p>若你的模板映射到 <code>/myshop</code>（非根路径），系统会<strong>自动重写</strong> HTML 中的绝对路径：</p>
<pre><code class="language-html">&lt;!-- 打包原始 --&gt;
&lt;script src="/assets/index.js"&gt;&lt;/script&gt;

&lt;!-- 访问 /myshop 时自动变为 --&gt;
&lt;script src="/myshop/assets/index.js"&gt;&lt;/script&gt;
</code></pre>
<p>支持自动重写的目录前缀：<code>/assets/</code>、<code>/static/</code>、<code>/js/</code>、<code>/css/</code>、<code>/images/</code>、<code>/img/</code>、<code>/fonts/</code>、<code>/libs/</code>、<code>/lib/</code></p>
<h3>5.2 获取当前路由前缀</h3>
<p>HTML 中可使用 <code>[router]</code> 占位符，服务端渲染时替换为当前路由：</p>
<pre><code class="language-html">&lt;div id="app" data-base="[router]"&gt;&lt;/div&gt;
&lt;!-- 映射到 /myshop 时变为 data-base="/myshop" --&gt;
</code></pre>
<p>JS 中读取：</p>
<pre><code class="language-javascript">const basePath = document.getElementById('app').dataset.base || '';
// 用于 Vue Router 的 base 配置
</code></pre>
<h3>5.3 常见踩坑</h3>
<table>
 <thead>
  <tr>
   <th>问题</th>
   <th>原因</th>
   <th>解决</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>页面白屏，JS 404</td>
   <td>静态资源绝对路径未重写</td>
   <td>确保 HTML 引用为 <code>/assets/xxx</code> 格式；或映射到根路由 <code>/</code></td>
  </tr>
  <tr>
   <td>JS 动态加载 404</td>
   <td>代码中硬编码了 <code>/assets/</code></td>
   <td>改为相对路径，或用 <code>[router]</code> 获取前缀</td>
  </tr>
  <tr>
   <td>接口跨域</td>
   <td>开发时前后端不同端口</td>
   <td>开发环境配置代理，或直连后端 <code>127.0.0.1:8080</code></td>
  </tr>
  <tr>
   <td>登录态丢失</td>
   <td>Token 未持久化</td>
   <td>写入 <code>localStorage</code>，请求头带 <code>user-token</code></td>
  </tr>
 </tbody>
</table>
<hr>
<h2>六、开发流程与上架规范</h2>
<h3>6.1 完整开发流程</h3>
<pre><code>1. 在授权站创建模板项目，配置 template_slug
       ↓
2. 本地开发前端（Vue / UniApp H5 等）
       ↓
3. 打包产物确保含 index.html + assets/
       ↓
4. 本地测试：手动创建路由映射指向打包目录
       ↓
5. 打包 zip 上传授权站，提交审核
       ↓
6. 站长在「模板商店」安装 → 启用路由映射
</code></pre>
<h3>6.2 本地调试建议</h3>
<p><strong>方式一：路由映射调试（推荐）</strong></p>
<ol>
 <li>将打包产物放到后端 <code>doc/my-template/</code> 目录</li>
 <li>管理后台 → 路由映射 → 新建映射
  <ul>
   <li>路由：<code>/my-template</code></li>
   <li>文件路径：<code>my-template/index.html</code></li>
   <li>设备类型：<code>pc</code> / <code>pe</code> / <code>def</code></li>
   <li>状态：启用</li>
  </ul></li>
 <li>访问 <code>http://127.0.0.1:8080/my-template</code> 验证</li>
</ol>
<p><strong>方式二：开发服务器 + API 直连</strong></p>
<pre><code class="language-javascript">// vite.config.js
export default {
  server: {
    proxy: {
      '/api': 'http://127.0.0.1:8080'
    }
  }
}
</code></pre>
<h3>6.3 提审清单</h3>
<p>提交模板商店审核前，请逐项确认：</p>
<ul>
 <li><input disabled type="checkbox"> 包含 <code>index.html</code>（或 <code>index.htm</code> / <code>index.php</code>）入口文件</li>
 <li><input disabled type="checkbox"> 预览图 <code>index.png</code>（建议 800×600px）</li>
 <li><input disabled type="checkbox"> 路由映射测试通过，静态资源全部 200</li>
 <li><input disabled type="checkbox"> 商品列表、详情、下单、查单核心链路跑通</li>
 <li><input disabled type="checkbox"> 登录 / 游客双模式均正常</li>
 <li><input disabled type="checkbox"> 移动端适配（若 <code>template_device=mobile</code>）</li>
 <li><input disabled type="checkbox"> <code>conf.json</code> 格式正确（若有配置项）</li>
 <li><input disabled type="checkbox"> 帮助文档 <code>help.html</code>（若有扩展功能）</li>
 <li><input disabled type="checkbox"> 无硬编码第三方域名、无安全隐患</li>
</ul>
<p><strong>上架参数：</strong></p>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>要求</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>项目类型</td>
   <td>选择「模板」</td>
  </tr>
  <tr>
   <td>安装路径</td>
   <td>填写 <code>/</code></td>
  </tr>
  <tr>
   <td>设备类型</td>
   <td><code>universal</code> / <code>mobile</code> / <code>desktop</code></td>
  </tr>
  <tr>
   <td>template_slug</td>
   <td>英文数字，全局唯一</td>
  </tr>
 </tbody>
</table>
<hr>
<h2>七、扩展开发方式</h2>
<h3>7.1 插件式扩展（复杂功能）</h3>
<p>需要拼团、砍价、自定义支付等能力时，建议开发<strong>应用商店插件</strong>，在模板中通过接口调用：</p>
<pre><code>GET ?AppApies&amp;identification={插件标识}&amp;name={方法名}
</code></pre>
<p>插件控制器规范：</p>
<pre><code>/includes/lib/soft/controller/{标识}/index.php
</code></pre>
<h3>7.2 内置式扩展（简单功能）</h3>
<p>在模板包内直接新增 PHP 文件，通过 URL 访问：</p>
<pre><code>/?mod={扩展文件名}
</code></pre>
<p>查找顺序：</p>
<ol>
 <li><code>/template/{应用标识}/{扩展文件}.php</code></li>
 <li><code>/template/default/{扩展文件}.php</code></li>
</ol>
<blockquote>
 <p>新架构下更推荐纯前端 + REST API 方案，PHP 扩展仅作兼容保留。</p>
</blockquote>
<hr>
<h2>八、开发规范与最佳实践</h2>
<h3>8.1 代码规范</h3>
<ol>
 <li><strong>不要硬编码站点域名</strong>，统一用 <code>window.location.origin</code></li>
 <li><strong>图片路径</strong>走 <code>/static/img/</code> 直链，不走 <code>/api/image/proxy</code></li>
 <li><strong>富文本渲染</strong>（UniApp PE 端）统一用 <code>&lt;u-parse :content="xxx" :selectable="true"&gt;</code></li>
 <li><strong>登录态</strong>优先读 <code>localStorage</code>，401 时引导重新登录</li>
 <li><strong>接口兼容</strong>同时判断 <code>code === 200</code> 和 <code>code === 1</code></li>
</ol>
<h3>8.2 性能建议</h3>
<ul>
 <li>静态资源打包压缩，图片适当 WebP</li>
 <li>商品列表分页加载，避免一次拉全量</li>
 <li>站点配置（<code>/api/inform</code>）可缓存 60 秒，开发模式建议 1 秒</li>
 <li>按需加载路由模块，控制首屏 JS 体积</li>
</ul>
<h3>8.3 安全建议</h3>
<ul>
 <li>不在前端存储敏感信息（密码、API 密钥）</li>
 <li>用户输入做 XSS 过滤后再渲染</li>
 <li>涉及资金的接口必须带 Token，不做前端金额计算终裁</li>
</ul>
<hr>
<h2>九、快速上手示例：最小可用模板</h2>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;我的商城模板&lt;/title&gt;
  &lt;style&gt;
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; background: #f5f5f5; }
    .header { background: #fff; padding: 16px; text-align: center; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
    .goods-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; padding: 16px; }
    .goods-card { background: #fff; border-radius: 8px; overflow: hidden; cursor: pointer; }
    .goods-card img { width: 100%; aspect-ratio: 1; object-fit: cover; }
    .goods-card .info { padding: 8px 12px; }
    .goods-card .name { font-size: 14px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .goods-card .price { color: #ff4d4f; font-weight: 600; margin-top: 4px; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="header"&gt;&lt;h1 id="site-name"&gt;加载中...&lt;/h1&gt;&lt;/div&gt;
  &lt;div class="goods-list" id="goods-list"&gt;&lt;/div&gt;

  &lt;script&gt;
    const API = window.location.origin;

    async function api(url, params = {}) {
      const token = localStorage.getItem('user-token');
      const headers = { 'Content-Type': 'application/json' };
      if (token) headers['user-token'] = 'Bearer ' + token.trim();
      const res = await fetch(API + url, { method: 'POST', headers, body: JSON.stringify(params) });
      return res.json();
    }

    async function init() {
      // 1. 站点信息
      const site = await api('/api/inform');
      if (site.code === 200 || site.code === 1) {
        document.getElementById('site-name').textContent = site.data?.sitename || '商城';
      }

      // 2. 商品列表
      const goods = await api('/api/getGoods', { page: 1, limit: 20 });
      const list = goods.data?.list || goods.data || [];
      const container = document.getElementById('goods-list');

      list.forEach(item =&gt; {
        const img = item.image?.startsWith('http') ? item.image : API + (item.image || '/static/img/default.png');
        container.innerHTML += `
          &lt;div class="goods-card" onclick="location.href='?id=${item.id}'"&gt;
            &lt;img src="${img}" alt="${item.name}" loading="lazy"&gt;
            &lt;div class="info"&gt;
              &lt;div class="name"&gt;${item.name}&lt;/div&gt;
              &lt;div class="price"&gt;¥${item.price}&lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;`;
      });
    }

    init().catch(err =&gt; console.error('初始化失败:', err));
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>将上述文件保存为 <code>index.html</code>，配合路由映射即可在本地验证。</p>
<hr>
<h2>十、相关资源</h2>
<table>
 <thead>
  <tr>
   <th>资源</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>前台接口文档</td>
   <td>项目 <code>doc/api/client/</code> 目录</td>
  </tr>
  <tr>
   <td>接口测试守则</td>
   <td><code>doc/api/接口测试守则.md</code></td>
  </tr>
  <tr>
   <td>路由映射静态资源说明</td>
   <td>后端 <code>UniApp路由映射静态资源解决方案.txt</code></td>
  </tr>
  <tr>
   <td>应用商店开发平台</td>
   <td><a href="https://appstor.79tian.com/">https://appstor.79tian.com/</a></td>
  </tr>
  <tr>
   <td>开发文档中心</td>
   <td><a href="https://appstor.79tian.com/docs">https://appstor.79tian.com/docs</a></td>
  </tr>
 </tbody>
</table>
<hr>
<h2>总结</h2>
<p>小氢云商城模板开发的本质是：<strong>用任意前端技术栈打包出 SPA，通过路由映射挂载到商城域名下，用标准 REST API 驱动业务</strong>。</p>
<p>记住三个关键点：</p>
<ol>
 <li><strong>入口文件必须叫 <code>index</code></strong>，静态资源与之间目录</li>
 <li><strong>接口走 <code>/api/*</code>，认证头用 <code>user-token: Bearer {token}</code></strong></li>
 <li><strong>非根路由映射时，依赖系统自动重写 <code>/assets/</code> 等静态路径</strong></li>
</ol>
<p>按本文规范开发并通过提审清单自检，你的模板就能顺利上架并被站长一键安装使用。</p>
<hr>
<p><em>如有疑问，欢迎在评论区留言或加入开发者交流群。</em></p>]]></description><guid isPermaLink="false">/archives/post-1780044669502</guid><dc:creator>Aツ韩先生</dc:creator><category>小氢云商城系统</category><category>小氢云</category><pubDate>Fri, 29 May 2026 09:49:53 GMT</pubDate></item><item><title><![CDATA[Apache 反向代理下配置 WebSocket 完整教程（宝塔面板 + Go 二进制）]]></title><link>https://bbs.ithxs.com/archives/post-1780043793691</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=Apache%20%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E4%B8%8B%E9%85%8D%E7%BD%AE%20WebSocket%20%E5%AE%8C%E6%95%B4%E6%95%99%E7%A8%8B%EF%BC%88%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%20%2B%20Go%20%E4%BA%8C%E8%BF%9B%E5%88%B6%EF%BC%89&amp;url=/archives/post-1780043793691" width="1" height="1" alt="" style="opacity:0;">
<h1>Apache 反向代理下配置 WebSocket 完整教程</h1>
<blockquote>
 <p>适用场景：后端为 <strong>Go 二进制</strong>（如监听 <code>127.0.0.1:8091</code>），前面使用 <strong>Apache</strong>（宝塔面板常见）做 HTTP/HTTPS 反向代理。
  <br>
  不装 Nginx 也可以，但 <strong>必须在 Apache 里单独处理 WebSocket 握手</strong>，否则会出现实时消息、售后聊天、通知推送连不上的问题。</p>
</blockquote>
<hr>
<h2>一、先搞懂：为什么会出现这条报错？</h2>
<p>后端日志里若看到类似：</p>
<blockquote>
 <p>WebSocket升级失败: websocket: the client is not using the websocket protocol:
  <br>
  'upgrade' token not found in 'Connection' header</p>
</blockquote>
<p>含义是：<strong>到达 Go 的请求是普通 HTTP GET，不是 WebSocket 握手</strong>。</p>
<p>常见原因分三类：</p>
<table>
 <thead>
  <tr>
   <th>类型</th>
   <th>说明</th>
   <th>是否影响业务</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>误访问</td>
   <td>浏览器地址栏直接打开 <code>http://域名/api/ws/...</code></td>
   <td>一般可忽略</td>
  </tr>
  <tr>
   <td>反代未配 WS</td>
   <td>Apache 只写了 <code>ProxyPass</code>，未转发 <code>Upgrade</code></td>
   <td><strong>会导致聊天/通知失效</strong></td>
  </tr>
  <tr>
   <td>探活/扫描</td>
   <td>监控工具用 HTTP 探测 WS 路径</td>
   <td>可忽略</td>
  </tr>
 </tbody>
</table>
<p><strong>结论</strong>：你即使用二进制启动 Go、不装 Nginx，只要域名前面有 <strong>Apache 反代</strong>，就必须给 WebSocket 单独加规则。</p>
<hr>
<h2>二、架构示意</h2>
<blockquote>
 <p>浏览器 --wss/https--&gt; Apache (80/443) --ws/http--&gt; Go 二进制 (127.0.0.1:8091)
  <br>
  普通页面/API：ProxyPass http://
  <br>
  WebSocket：需 Upgrade 转发到 ws://</p>
</blockquote>
<p>本项目中 WebSocket 入口示例：</p>
<ul>
 <li>售后/全局消息：<code>/api/ws/aftersale?token=你的JWT</code></li>
 <li>接单大厅：<code>/api/ws/lobby?token=你的JWT</code></li>
</ul>
<p>前端在 HTTPS 页面下应连接：<code>wss://你的域名/api/ws/aftersale?token=xxx</code></p>
<hr>
<h2>三、前置条件检查清单</h2>
<p>在改配置前，请确认：</p>
<ol>
 <li><strong>Go 服务已监听</strong> <code>8091</code>（或你自定义端口），本机可访问：
  <br>
  <code>curl http://127.0.0.1:8091/health</code></li>
 <li><strong>防火墙/安全组</strong> 已放行对外 80、443（Apache）；8091 通常只本机访问，不必对公网开放。</li>
 <li><strong>站点已配置反代</strong> 到 <code>http://127.0.0.1:8091/</code>（你当前的宝塔写法）。</li>
 <li>若站点启用 <strong>HTTPS</strong>，必须同时改 <strong>443</strong> 的 VirtualHost，不能只改 80。</li>
</ol>
<hr>
<h2>四、第一步：启用 Apache 模块（宝塔）</h2>
<p>路径：<strong>软件商店 → Apache → 设置 → 模块 / 加载模块</strong></p>
<p>请确保已启用：</p>
<ul>
 <li><code>proxy</code></li>
 <li><code>proxy_http</code></li>
 <li><strong><code>proxy_wstunnel</code></strong>（WebSocket 关键模块）</li>
 <li><code>rewrite</code></li>
</ul>
<p>保存后 <strong>重载配置</strong> 或 <strong>重启 Apache</strong>。</p>
<blockquote>
 <p>若没有 <code>proxy_wstunnel</code>，<code>RewriteRule ... ws://... [P]</code> 无法生效，WebSocket 一定失败。</p>
</blockquote>
<hr>
<h2>五、第二步：修改站点 Apache 配置（核心）</h2>
<h3>5.1 找到配置位置</h3>
<p>宝塔：<strong>网站 → 你的站点 → 设置 → 配置文件</strong>（Apache）。</p>
<p>找到类似段落：</p>
<pre><code class="language-apache"># HTTP反向代理相关配置开始 &gt;&gt;&gt;
...
# HTTP反向代理相关配置结束 &lt;&lt;&lt;
</code></pre>
<h3>5.2 推荐完整写法（80 端口示例）</h3>
<p>将中间反代段 <strong>整体替换</strong> 为：</p>
<pre><code class="language-apache"># HTTP反向代理相关配置开始 &gt;&gt;&gt;
&lt;IfModule mod_proxy.c&gt;
    ProxyRequests Off
    SSLProxyEngine on
    ProxyPreserveHost On

    # ---------- WebSocket 升级转发（必须） ----------
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/(.*) ws://127.0.0.1:8091/$1 [P,L]

    # ---------- 普通 HTTP 请求 ----------
    ProxyPass / http://127.0.0.1:8091/
    ProxyPassReverse / http://127.0.0.1:8091/
&lt;/IfModule&gt;
# HTTP反向代理相关配置结束 &lt;&lt;&lt;
</code></pre>
<p><strong>说明：</strong></p>
<ul>
 <li><code>8091</code> 改成你 Go 程序实际监听端口。</li>
 <li><code>RewriteRule</code> 必须在 <code>ProxyPass</code> 之前生效：带 <code>Upgrade: websocket</code> 的请求走 <code>ws://</code>，其余走普通 HTTP。</li>
 <li><code>[P,L]</code> 表示通过代理模块处理并停止后续规则。</li>
</ul>
<h3>5.3 你原来的配置缺了什么？</h3>
<p>仅下面两行时，<strong>页面和 API 正常，WebSocket 会失败</strong>：</p>
<pre><code class="language-apache">ProxyPass / http://127.0.0.1:8091/
ProxyPassReverse / http://127.0.0.1:8091/
</code></pre>
<p>因为 Apache 默认不会把 WebSocket 握手头转发成后端所需的 <code>Connection: Upgrade</code>。</p>
<hr>
<h2>六、HTTPS（443）站点也要同样配置</h2>
<p>若用户访问 <code>https://pp.example.com</code>，浏览器 WebSocket 使用 <strong>wss</strong>，走的是 <strong>443</strong> 的 VirtualHost。</p>
<p>请务必在 <strong>SSL 站点</strong> 的配置里加入 <strong>与 80 相同</strong> 的 <code>RewriteEngine</code> + <code>ProxyPass</code> 段。</p>
<p>只改 80、不改 443 的典型现象：</p>
<ul>
 <li>网站能打开（https）</li>
 <li>接口大部分正常</li>
 <li><strong>实时消息、未读气泡、聊天一直连不上</strong></li>
</ul>
<hr>
<h2>七、验证是否配置成功</h2>
<h3>7.1 浏览器开发者工具</h3>
<ol>
 <li>登录你的前台/后台页面。</li>
 <li>按 <code>F12</code> → <strong>网络 (Network)</strong> → 筛选 <strong>WS</strong>。</li>
 <li>找到类似：<code>/api/ws/aftersale?token=...</code></li>
 <li>正常状态应为：<strong>101 Switching Protocols</strong>（已建立 WebSocket）。</li>
</ol>
<p>若显示失败、一直 pending、或很快断开，优先检查 Apache 模块与 443 配置。</p>
<h3>7.2 看 Go 日志</h3>
<p>配置正确后：</p>
<ul>
 <li>不应再频繁刷 <code>upgrade token not found</code>（偶发误访问可忽略）。</li>
 <li>连接成功会有业务侧日志（视项目而定）。</li>
</ul>
<h3>7.3 本机直连对比（排查用）</h3>
<p>在服务器上：</p>
<pre><code class="language-bash">curl http://127.0.0.1:8091/health
</code></pre>
<p>若本机正常、域名不正常，问题在 <strong>Apache 反代层</strong>，不是 Go 程序本身。</p>
<hr>
<h2>八、与「不装 Nginx、二进制启动」的关系</h2>
<table>
 <thead>
  <tr>
   <th>说法</th>
   <th>是否正确</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>Go 二进制直接跑，可以不装 Nginx</td>
   <td>对</td>
  </tr>
  <tr>
   <td>前面没有反代，浏览器直连 IP:8091</td>
   <td>一般不需要 Apache 配置</td>
  </tr>
  <tr>
   <td>域名走宝塔 Apache 反代到 8091</td>
   <td><strong>必须配 WebSocket</strong>，与是否 Nginx 无关</td>
  </tr>
 </tbody>
</table>
<p>你当前属于第三种：<strong>Apache 反代 + Go 二进制</strong>，按本文配置即可，无需再装 Nginx。</p>
<hr>
<h2>九、前端连接注意事项（开发参考）</h2>
<ol>
 <li><strong>协议跟随页面</strong>：HTTPS 页面必须用 <code>wss://</code>，HTTP 用 <code>ws://</code>。</li>
 <li><strong>Token 建议 URL 编码</strong>：<code>token=${encodeURIComponent(jwt)}</code>，避免特殊字符截断。</li>
 <li><strong>路径与后端一致</strong>：例如 <code>/api/ws/aftersale</code>。</li>
</ol>
<p>示例：</p>
<pre><code class="language-javascript">const wsUrl = `wss://${location.host}/api/ws/aftersale?token=${encodeURIComponent(token)}`
const ws = new WebSocket(wsUrl)
</code></pre>
<hr>
<h2>十、常见问题 FAQ</h2>
<h3>Q1：配了 Rewrite 仍连不上？</h3>
<ul>
 <li>确认 <code>proxy_wstunnel</code> 已启用并重载 Apache。</li>
 <li>确认改的是 <strong>用户实际访问的端口</strong>（https 改 443）。</li>
 <li>确认 Go 监听地址是 <code>127.0.0.1:8091</code> 与配置一致。</li>
</ul>
<h3>Q2：只有 WebSocket 报错，网站正常？</h3>
<p>说明 HTTP 反代 OK，<strong>仅缺 WS 规则</strong>，按第五节补 <code>RewriteEngine</code> 即可。</p>
<h3>Q3：可以用宝塔「反向代理」界面吗？</h3>
<p>可以填目标 URL <code>http://127.0.0.1:8091</code>，有 <strong>WebSocket 开关请打开</strong>；但 Apache 环境下仍建议在 <strong>配置文件</strong> 中保留 <code>RewriteRule</code>，最稳妥。</p>
<h3>Q4：需要给 8091 开端口给外网吗？</h3>
<p>通常 <strong>不需要</strong>。外网只访问 80/443，由 Apache 转发到本机 8091。</p>
<h3>Q5：和 Nginx 的配置等价吗？</h3>
<p>思路相同：都要转发 <code>Upgrade</code> 和 <code>Connection</code>。Nginx 写法是 <code>proxy_set_header Upgrade $http_upgrade;</code> 等；Apache 用本文 <code>RewriteRule</code> + <code>proxy_wstunnel</code>。</p>
<hr>
<h2>十一、配置模板速查</h2>
<p><strong>仅本机 Go、无反代：</strong> <code>ws://服务器IP:8091/api/ws/aftersale?token=xxx</code></p>
<p><strong>域名 + Apache + HTTPS：</strong> <code>wss://你的域名/api/ws/aftersale?token=xxx</code></p>
<p><strong>Apache 反代核心三行逻辑：</strong></p>
<ol>
 <li><code>RewriteEngine On</code></li>
 <li>检测 <code>Upgrade=websocket</code> → 转发到 <code>ws://127.0.0.1:端口</code></li>
 <li>其余请求 <code>ProxyPass</code> 到 <code>http://127.0.0.1:端口</code></li>
</ol>
<hr>
<h2>十二、小结</h2>
<ul>
 <li><strong>不装 Nginx、二进制跑 Go</strong> 完全可行。</li>
 <li>只要 <strong>Apache（宝塔）反代</strong> 到你的 Go 端口，就必须配置 <strong>WebSocket 升级转发</strong>。</li>
 <li>核心是启用 <strong><code>proxy_wstunnel</code></strong>，并添加 <strong><code>RewriteRule ... ws://... [P,L]</code></strong>。</li>
 <li><strong>HTTPS 站点（443）</strong> 与 HTTP（80）要同步修改。</li>
 <li>用浏览器 WS 面板看到 <strong>101</strong> 即表示成功。</li>
</ul>
<p>按本文操作后，实时聊天、消息推送、接单大厅等依赖 WebSocket 的功能即可与 API 一样稳定对外服务。</p>
<hr>
<p><em>本文由小氢云相关部署实践整理，如有环境差异（OpenLiteSpeed、CDN 等）可在评论补充。</em></p>]]></description><guid isPermaLink="false">/archives/post-1780043793691</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-ezjl.jpg&amp;size=m" type="image/jpeg" length="62061"/><pubDate>Fri, 29 May 2026 08:47:20 GMT</pubDate></item><item><title><![CDATA[小氢云主机系统工作流教程]]></title><link>https://bbs.ithxs.com/archives/019e5edc-e47a-74cb-8bbc-d28173287ced</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E4%B8%BB%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%B7%A5%E4%BD%9C%E6%B5%81%E6%95%99%E7%A8%8B&amp;url=/archives/019e5edc-e47a-74cb-8bbc-d28173287ced" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E8%87%AA%E5%B8%A6%E8%8A%82%E7%82%B9%E5%87%BD%E6%95%B0">自带节点函数</h1>
<p style="">系统里提供了一些自带的节点函数，比如</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-hpwv.png&amp;size=m" width="602px">
</figure>
<p style=""></p>
<p style="">点一下节点函数就可以查看详细信息</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-jomB.png&amp;size=m" width="2380px">
</figure>
<p style=""></p>
<p style=""></p>
<p style="">我给你们写了一个 一键部署 Go 程序的脚本，你们可以参考一下，功能正常可以直接使用</p>
<p style="">脚本里有一段是上传文件，用的是 @，@的意思就是 /doc 目录！也就是下面的这张图，你要把你部署的程序安装包放进去</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-QdsE.png&amp;size=m" width="2120px">
</figure>
<p style=""></p>
<p style=""></p>
<pre><code class="language-javascript">/**
 * jjwk-service 一键部署工作流
 *
 * 执行顺序（幂等设计，可重复运行）：
 *   [01] 初始化 → 拉取主机/用户信息，弹窗选择或输入绑定域名
 *   [02] 部署包 → 远程无 zip 才上传，上传后二次校验
 *   [03] 解压   → 二进制不存在，或刚上传且 forceUnzipAfterUpload=true 时解压
 *   [04] 数据库 → 不存在则创建，存在则复用主机记录中的账号信息
 *   [05] 配置   → 写 config.yaml（数据库 + app.base_url）
 *   [06] 端口   → 防火墙放行服务端口（已放行则跳过）
 *   [07] Go项目 → 查重后创建/跳过，本地绑定域名，启动项目
 *   [08] 通知   → 有邮箱则发 HTML 邮件汇总
 *
 * 注意：
 *   - 工作流 API 参数名（host_id、host_source 等）为 snake_case，与后端一致
 *   - 脚本内部变量统一 camelCase
 *   - hostId 可在编辑器顶部「执行主机 ID」覆盖，此处为默认值
 */

// ========== 配置 ==========

/** 目标主机 ID（与编辑器顶部 host_id 注入二选一，此处为脚本内默认值） */
const hostId = "";

/** 本地部署包路径（@/ 表示系统 doc 目录下的相对路径） */
const localPackagePath = "@/jjwk-service-v2.4.8-install.zip";

/** 上传到远程站点目录后的 zip 文件名 */
const packageFileName = "jjwk-service-v2.4.8-install.zip";

/** 解压后的 Go 二进制文件名（需与 zip 内一致） */
const binaryFileName = "jjwk-service";

/** 主机未配置端口时的默认监听端口 */
const defaultPort = 9001;

/**
 * 上传新包后是否强制重新解压
 * true  = 即使二进制已存在，刚上传完也会覆盖解压
 * false = 二进制已存在则跳过解压
 */
const forceUnzipAfterUpload = true;

// ========== 日志工具 ==========

/** 全局递增序号，供 logInfo 使用 */
var logSequence = 0;

/** 序号补零：1 → "01"，12 → "12" */
function padLogIndex(value) {
  return value &lt; 10 ? "0" + value : String(value);
}

/**
 * 主步骤日志，自动递增序号
 * 示例：[03] 远程包已存在，跳过上传
 */
function logInfo(message) {
  logSequence += 1;
  console.log("[" + padLogIndex(logSequence) + "] " + message);
}

/**
 * 子步骤/细节日志，带树形缩进，不占用主序号
 * 示例：         └─ siteRootPath: /www/wwwroot/xxx
 */
function logDetail(message) {
  console.log("         └─ " + message);
}

/**
 * 阶段标题日志，使用固定阶段号（01~08），便于对照流程图
 * 示例：[02] 检测并上传部署包
 */
function logPhase(phaseNo, title) {
  console.log("[" + padLogIndex(phaseNo) + "] " + title);
}

// ========== 通用工具 ==========

/**
 * 将主机名转为宝塔 Go 项目名
 * 宝塔不允许项目名含 `.` 和 `-`，统一替换为 `_`
 */
function sanitizeGoProjectName(hostName) {
  return String(hostName || "")
    .replace(/\./g, "_")
    .replace(/-/g, "_");
}

/**
 * 将主机名转为 MySQL 库名/用户名
 * MySQL 标识符不支持 `.` 和 `-`，规则同 Go 项目名
 */
function sanitizeDbName(hostName) {
  return String(hostName || "")
    .replace(/\./g, "_")
    .replace(/-/g, "_");
}

/**
 * 生成随机数据库密码（排除易混淆字符 0/O/1/l/I）
 * @param {number} length 密码长度
 */
function randomDbPassword(length) {
  var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
  var password = "";
  for (var i = 0; i &lt; length; i++) {
    password += charset.charAt(Math.floor(Math.random() * charset.length));
  }
  return password;
}

/**
 * 规范化用户输入的域名
 * - 去掉首尾空白、末尾 /
 * - 去掉 http(s):// 协议头
 * - 去掉路径部分（只保留 host）
 * 不使用正则匹配协议，避免 Monaco 校验误报
 */
function normalizeDomainInput(value) {
  var normalized = String(value || "").trim();
  while (normalized.length &gt; 0 &amp;&amp; normalized.charAt(normalized.length - 1) === "/") {
    normalized = normalized.slice(0, -1);
  }
  var lowerCase = normalized.toLowerCase();
  if (lowerCase.indexOf("https:") === 0) {
    normalized = normalized.slice(6);
    if (normalized.charAt(0) === "/" &amp;&amp; normalized.charAt(1) === "/") {
      normalized = normalized.slice(2);
    }
  } else if (lowerCase.indexOf("http:") === 0) {
    normalized = normalized.slice(5);
    if (normalized.charAt(0) === "/" &amp;&amp; normalized.charAt(1) === "/") {
      normalized = normalized.slice(2);
    }
  }
  var slashIndex = normalized.indexOf("/");
  if (slashIndex &gt;= 0) {
    normalized = normalized.slice(0, slashIndex);
  }
  return normalized.trim();
}

/**
 * 断言工作流节点返回 status === true，否则抛错中断流程
 * @param {object} actionResult 节点返回值
 * @param {string} actionName   节点名，用于错误信息
 */
function assertActionSuccess(actionResult, actionName) {
  if (!actionResult || actionResult.status !== true) {
    throw new Error(
      actionName + " 失败: " + JSON.stringify(actionResult || { status: false }),
    );
  }
  return actionResult;
}

/**
 * 解析 database_exists 返回值
 * 该节点包装为 { value: true/false, status, code }
 */
function isDatabaseExists(checkResult) {
  return !!(checkResult &amp;&amp; checkResult.value === true);
}

// ========== Go 项目查重 ==========

/** 从宝塔 Go 项目对象中提取项目名称（兼容多种字段名） */
function resolveGoProjectName(projectItem) {
  if (!projectItem) {
    return "";
  }
  var configName =
    projectItem.project_config &amp;&amp;
    (projectItem.project_config.project_name ||
      projectItem.project_config.projectName);
  return String(
    projectItem.name ||
    projectItem.project_name ||
    projectItem.projectName ||
    configName ||
    "",
  ).trim();
}

/** 名称比较（忽略大小写） */
function isSameGoProjectName(left, right) {
  return (
    !!String(left || "").trim() &amp;&amp;
    String(left).trim().toLowerCase() === String(right || "").trim().toLowerCase()
  );
}

/** 在列表中查找与当前主机匹配的 Go 项目 */
function findGoProjectInList(projectList, projectName, hostName, siteRootPath) {
  for (var itemIndex = 0; itemIndex &lt; projectList.length; itemIndex++) {
    if (
      isGoProjectMatched(
        projectList[itemIndex],
        projectName,
        hostName,
        siteRootPath,
      )
    ) {
      return projectList[itemIndex];
    }
  }
  return null;
}

/**
 * 创建 Go 项目后校验：以宝塔项目列表为准（get_project_info 可能误判）
 */
function verifyGoProjectExists(projectName, hostName, siteRootPath, retryTimes) {
  var maxRetry = retryTimes == null ? 5 : retryTimes;
  for (var attemptIndex = 0; attemptIndex &lt; maxRetry; attemptIndex++) {
    var matched = findGoProject(projectName, hostName, siteRootPath);
    if (matched) {
      logDetail("Go 项目校验通过: " + resolveGoProjectName(matched));
      return matched;
    }
    if (attemptIndex &lt; maxRetry - 1) {
      logDetail(
        "Go 项目暂未出现在列表（" +
        (attemptIndex + 1) +
        "/" +
        maxRetry +
        "），2 秒后重试…",
      );
      delay({ seconds: 2 });
    }
  }
  throw new Error("Go 项目校验失败，宝塔列表中未找到: " + projectName);
}

/**
 * 统一 get_go_project_list 各种返回结构为数组
 * 兼容 list / items / data / value 等字段
 */
function normalizeGoProjectList(rawList) {
  if (!rawList) {
    return [];
  }
  if (Array.isArray(rawList)) {
    return rawList;
  }
  if (Array.isArray(rawList.list)) {
    return rawList.list;
  }
  if (Array.isArray(rawList.items)) {
    return rawList.items;
  }
  if (Array.isArray(rawList.data)) {
    return rawList.data;
  }
  if (Array.isArray(rawList.value)) {
    return rawList.value;
  }
  return [];
}

/** 从宝塔 Go 项目对象中提取项目路径，去掉末尾 / */
function resolveGoProjectPath(projectItem) {
  return String((projectItem &amp;&amp; projectItem.path) || "").trim().replace(/\/+$/, "");
}

/**
 * 按关键字搜索 Go 项目列表（宝塔侧模糊搜索）
 * @param {string} searchKeyword 搜索词，通常为主机名或 sanitize 后的项目名
 * @param {number} resultLimit    最多返回条数
 */
function searchGoProjects(searchKeyword, resultLimit) {
  var keyword = String(searchKeyword || "").trim();
  if (!keyword) {
    return [];
  }
  return normalizeGoProjectList(
    get_go_project_list({
      search: keyword,
      p: 1,
      limit: resultLimit || 10,
      type_id: "",
    }),
  );
}

/**
 * 判断列表中的 Go 项目是否与当前主机匹配
 * 匹配规则（任一满足即可）：
 *   1. 项目名 === sanitizeGoProjectName(hostName)
 *   2. 项目名 === hostName
 *   3. 项目路径 === siteRootPath
 */
function isGoProjectMatched(projectItem, projectName, hostName, siteRootPath) {
  var normalizedSitePath = String(siteRootPath || "").trim().replace(/\/+$/, "");
  var resolvedName = resolveGoProjectName(projectItem);
  var resolvedPath = resolveGoProjectPath(projectItem);
  var sanitizedHostName = sanitizeGoProjectName(hostName);

  if (normalizedSitePath &amp;&amp; resolvedPath &amp;&amp; resolvedPath === normalizedSitePath) {
    return true;
  }
  if (isSameGoProjectName(resolvedName, projectName)) {
    return true;
  }
  if (isSameGoProjectName(resolvedName, hostName)) {
    return true;
  }
  if (isSameGoProjectName(resolvedName, sanitizedHostName)) {
    return true;
  }
  return false;
}

/**
 * 查找已存在的 Go 项目（避免重复创建）
 * 仅以 get_go_project_list 为准；get_go_project_detail 易误判导致跳过创建后无法启动
 * @returns {object|null} 匹配到的列表项，未找到返回 null
 */
function findGoProject(projectName, hostName, siteRootPath) {
  var searchKeywords = [];
  if (projectName) {
    searchKeywords.push(projectName);
  }
  if (hostName &amp;&amp; hostName !== projectName) {
    searchKeywords.push(hostName);
  }
  var sanitizedHostName = sanitizeGoProjectName(hostName);
  if (
    sanitizedHostName &amp;&amp;
    sanitizedHostName !== projectName &amp;&amp;
    sanitizedHostName !== hostName
  ) {
    searchKeywords.push(sanitizedHostName);
  }

  var keywordIndex;
  for (keywordIndex = 0; keywordIndex &lt; searchKeywords.length; keywordIndex++) {
    var projectList = searchGoProjects(searchKeywords[keywordIndex], 100);
    logDetail(
      "get_go_project_list search=" +
      searchKeywords[keywordIndex] +
      " 共 " +
      projectList.length +
      " 条",
    );
    var matched = findGoProjectInList(
      projectList,
      projectName,
      hostName,
      siteRootPath,
    );
    if (matched) {
      logDetail(
        "get_go_project_list 命中: " +
        resolveGoProjectName(matched) +
        " | path=" +
        resolveGoProjectPath(matched),
      );
      return matched;
    }
  }

  var fullList = normalizeGoProjectList(
    get_go_project_list({
      p: 1,
      limit: 1000,
      type_id: "",
    }),
  );
  logDetail("get_go_project_list 全量扫描 共 " + fullList.length + " 条");
  var fullMatched = findGoProjectInList(
    fullList,
    projectName,
    hostName,
    siteRootPath,
  );
  if (fullMatched) {
    logDetail(
      "get_go_project_list 全量命中: " + resolveGoProjectName(fullMatched),
    );
    return fullMatched;
  }

  return null;
}

// ========== 域名处理 ==========

/**
 * 从 get_host_info 返回中解析已绑定域名列表
 * domains 字段可能是 JSON 字符串、数组或逗号分隔文本
 */
function parseBoundDomains(hostInfo) {
  if (!hostInfo) {
    return [];
  }

  var rawDomains = hostInfo.domains;
  var domainList = [];

  if (Array.isArray(rawDomains)) {
    domainList = rawDomains;
  } else if (typeof rawDomains === "string" &amp;&amp; rawDomains.trim()) {
    try {
      var parsedDomains = JSON.parse(rawDomains);
      if (Array.isArray(parsedDomains)) {
        domainList = parsedDomains;
      } else if (typeof parsedDomains === "string" &amp;&amp; parsedDomains.trim()) {
        domainList = [parsedDomains];
      }
    } catch (parseError) {
      domainList = rawDomains.split(/[,，\s]+/);
    }
  }

  var boundDomains = [];
  for (var domainIndex = 0; domainIndex &lt; domainList.length; domainIndex++) {
    var domainValue = String(domainList[domainIndex] || "").trim();
    if (domainValue) {
      boundDomains.push(domainValue);
    }
  }

  return boundDomains;
}

/**
 * 判断域名是否已在当前主机本地绑定（大小写不敏感）
 * 用于区分「复用已有域名」与「绑定新域名」的日志/邮件文案
 */
function isDomainAlreadyBound(hostInfo, targetDomain) {
  var normalizedTarget = normalizeDomainInput(targetDomain).toLowerCase();
  if (!normalizedTarget) {
    return false;
  }
  var boundDomains = parseBoundDomains(hostInfo);
  for (var domainIndex = 0; domainIndex &lt; boundDomains.length; domainIndex++) {
    if (normalizeDomainInput(boundDomains[domainIndex]).toLowerCase() === normalizedTarget) {
      return true;
    }
  }
  return false;
}

/**
 * 调用 bind_user_domains_local 写入 lh_hosts.domains（不操作宝塔）
 * 后端会自动去重：同主机 / 该用户其他主机已绑定的域名会 skipped
 * @returns {{ domainBound: boolean, bindResult: object }}
 */
function bindDomainLocally(hostInfo, targetDomain) {
  var bindResult = bind_user_domains_local({
    user_id: hostInfo.uid,
    host_id: hostInfo.id,
    domains: [targetDomain],
  });

  if (bindResult.skipped) {
    logDetail("本地绑定跳过（域名已存在）: " + targetDomain);
    return { domainBound: false, bindResult: bindResult };
  }

  logDetail(
    "本地绑定成功: " +
    targetDomain +
    "，当前共 " +
    bindResult.total_count +
    " 个域名",
  );
  return { domainBound: true, bindResult: bindResult };
}

/**
 * 弹窗让用户选择已有域名或输入新域名（combobox 可输入下拉）
 * 暂停工作流等待用户操作；取消则 end_workflow 终止
 * @returns {{ bindDomain: string, appBaseUrl: string }}
 */
function requestBindDomain(hostInfo) {
  var boundDomains = parseBoundDomains(hostInfo);

  // 根据主机 SSL 配置决定 app.base_url 协议
  var enableHttps =
    hostInfo &amp;&amp;
    (hostInfo.ssl_enabled === 1 ||
      hostInfo.ssl_enabled === true ||
      hostInfo.ssl_force_https === 1 ||
      hostInfo.ssl_force_https === true);
  var urlScheme = enableHttps ? "https" : "http";

  // 已绑定域名作为 combobox 下拉选项
  var domainOptions = [];
  for (var optionIndex = 0; optionIndex &lt; boundDomains.length; optionIndex++) {
    domainOptions.push({
      label: boundDomains[optionIndex],
      value: boundDomains[optionIndex],
    });
  }

  var promptContent =
    "请选择已绑定域名，或直接输入新域名，将用于 app.base_url 与 Go 项目";
  if (boundDomains.length === 0) {
    promptContent = "请输入要绑定的域名，将用于 app.base_url 与 Go 项目";
  }

  var inputResult = request_user_input({
    title: "绑定域名",
    content: promptContent,
    input_type: "combobox",
    placeholder: "example.com",
    options: domainOptions,
    required: true,
    field_key: "domain",
  });

  // 用户点击取消 → 优雅结束工作流（非报错）
  if (inputResult.cancelled) {
    end_workflow({ message: "用户取消了域名输入" });
  }

  var selectedDomain = normalizeDomainInput(inputResult.value);
  if (!selectedDomain) {
    throw new Error("域名不能为空");
  }

  var applicationBaseUrl = urlScheme + "://" + selectedDomain;

  logDetail("用户选定域名: " + selectedDomain);
  logDetail("app.base_url: " + applicationBaseUrl);

  return {
    bindDomain: selectedDomain,
    appBaseUrl: applicationBaseUrl,
  };
}

// ========== [01] 初始化部署上下文 ==========
logPhase(1, "初始化部署上下文");

// 拉取主机详情（站点目录、端口、数据库信息、已绑定域名等）
var hostInfo = get_host_info({
  host_source: "custom",
  host_id: hostId,
});

if (!hostInfo || !hostInfo.host_name) {
  throw new Error("获取主机信息失败，host_name 为空");
}

// 拉取用户信息（邮件通知、本地域名绑定的 user_id 校验）
var userProfile = get_user_info({
  user_id: hostInfo.uid,
});

logInfo("主机: " + hostInfo.host_name + " | 用户: " + userProfile.username);

// 弹窗选择/输入域名，生成 appBaseUrl 供后续 config.yaml 与 Go 项目使用
var domainSelection = requestBindDomain(hostInfo);
var appBaseUrl = domainSelection.appBaseUrl;
var bindDomain = domainSelection.bindDomain;
var isExistingDomain = isDomainAlreadyBound(hostInfo, bindDomain);

if (isExistingDomain) {
  logInfo("域名策略: 复用已有绑定域名 → " + bindDomain);
} else {
  logInfo("域名策略: 绑定新域名 → " + bindDomain);
}

// 派生路径与运行时参数（均基于 host_name）
var siteRootPath = "/www/wwwroot/" + hostInfo.host_name;
var remotePackagePath = siteRootPath + "/" + packageFileName;
var binaryFilePath = siteRootPath + "/" + binaryFileName;
var configFilePath = siteRootPath + "/config/config.yaml";
var goProjectName = sanitizeGoProjectName(hostInfo.host_name);
var servicePort =
  hostInfo.port &amp;&amp; hostInfo.port &gt; 0 ? hostInfo.port : defaultPort;

logDetail("siteRootPath: " + siteRootPath);
logDetail("remotePackagePath: " + remotePackagePath);
logDetail("binaryFilePath: " + binaryFilePath);
logDetail("configFilePath: " + configFilePath);
logDetail("goProjectName: " + goProjectName + " | servicePort: " + servicePort);

// ========== [02] 检测并上传部署包 ==========
logPhase(2, "检测并上传部署包");

var packageExistsResult = check_file_exists({
  host_source: "custom",
  host_id: hostId,
  dirPath: siteRootPath,
  fileName: packageFileName,
  searchAll: false,
});

var uploadResult = null;
var packageUploaded = false;

if (packageExistsResult.found) {
  // 远程已有同名 zip → 跳过上传（幂等）
  logInfo("远程包已存在，跳过上传: " + packageFileName);
} else {
  logInfo("远程包不存在，开始上传: " + localPackagePath + " → " + packageFileName);

  uploadResult = upload_file({
    host_source: "custom",
    host_id: hostId,
    targetDir: siteRootPath,
    fileName: packageFileName,
    content_source: "local",
    content: localPackagePath,
  });

  if (!uploadResult || !uploadResult.status || uploadResult.progress !== 100) {
    throw new Error(
      "上传未完成: status=" +
      (uploadResult &amp;&amp; uploadResult.status) +
      " progress=" +
      (uploadResult &amp;&amp; uploadResult.progress),
    );
  }

  packageUploaded = true;
  logDetail("上传进度: " + uploadResult.progress + "%");
  logDetail("文件大小: " + uploadResult.file_size + " bytes");
  logDetail("远程路径: " + uploadResult.remote_path);

  // 上传完成后二次校验，防止静默失败
  var packageVerifyResult = check_file_exists({
    host_source: "custom",
    host_id: hostId,
    dirPath: siteRootPath,
    fileName: packageFileName,
    searchAll: false,
  });

  if (!packageVerifyResult.found) {
    throw new Error("上传后校验失败，远程未找到: " + packageFileName);
  }

  logInfo("部署包上传并校验通过");
}

// ========== [03] 解压并校验二进制 ==========
logPhase(3, "解压并校验二进制");

var binaryExistsResult = check_file_exists({
  host_source: "custom",
  host_id: hostId,
  dirPath: siteRootPath,
  fileName: binaryFileName,
  searchAll: false,
});

// 解压条件：二进制不存在，或刚上传了新包且配置了强制解压
var shouldExtractPackage = !binaryExistsResult.found || (packageUploaded &amp;&amp; forceUnzipAfterUpload);

if (!shouldExtractPackage) {
  logInfo("二进制已存在且无需覆盖，跳过解压: " + binaryFileName);
} else {
  logInfo("开始解压: " + remotePackagePath + " → " + siteRootPath);

  unzip_files({
    host_source: "custom",
    host_id: hostId,
    zipPath: remotePackagePath,
    targetDir: siteRootPath,
    password: "",
  });

  logDetail("解压完成，开始校验二进制");

  var binaryVerifyResult = check_file_exists({
    host_source: "custom",
    host_id: hostId,
    dirPath: siteRootPath,
    fileName: binaryFileName,
    searchAll: false,
  });

  if (!binaryVerifyResult.found) {
    throw new Error("解压后未找到二进制: " + binaryFilePath);
  }

  logDetail("二进制校验通过: " + binaryFilePath);

  // Go 二进制需 www 用户可执行
  set_file_access({
    host_source: "custom",
    host_id: hostId,
    filename: binaryFilePath,
    user: "www",
    access: "755",
  });

  logInfo("二进制权限已设置为 755");
}

// ========== [04] 检查并初始化数据库 ==========
logPhase(4, "检查并初始化数据库");

var databaseName = sanitizeDbName(hostInfo.host_name);
var databaseUser = databaseName;
var databasePassword = hostInfo.db_password || "";
var databaseCreated = false;

logDetail("目标库名: " + databaseName);

var databaseCheckResult = database_exists({
  dbName: databaseName,
});

if (isDatabaseExists(databaseCheckResult)) {
  logInfo("数据库已存在，跳过创建: " + databaseName);

  // 库已存在时，优先使用主机记录里保存的真实连接信息
  if (hostInfo.db_name) {
    databaseName = hostInfo.db_name;
  }
  if (hostInfo.db_account) {
    databaseUser = hostInfo.db_account;
  }
  if (hostInfo.db_password) {
    databasePassword = hostInfo.db_password;
  }

  if (!databasePassword) {
    logDetail("警告: 数据库已存在但主机记录缺少 db_password，配置可能不完整");
  }
} else {
  // 新建库时若主机无密码记录，自动生成随机密码
  if (!databasePassword) {
    databasePassword = randomDbPassword(16);
  }

  logInfo("数据库不存在，开始创建: " + databaseName);

  var createDatabaseResult = create_database({
    dbName: databaseName,
    dbUser: databaseUser,
    dbPassword: databasePassword,
  });

  assertActionSuccess(createDatabaseResult, "create_database");

  var databaseVerifyResult = database_exists({
    dbName: databaseName,
  });

  if (!isDatabaseExists(databaseVerifyResult)) {
    throw new Error("创建数据库后校验失败: " + databaseName);
  }

  databaseCreated = true;
  logInfo("数据库创建成功: " + databaseName + " | 用户: " + databaseUser);
}

// ========== [05] 更新应用配置文件 ==========
logPhase(5, "更新应用配置文件");

// 小氢云商城 config.yaml 字段结构
var configReplacements = {
  "database.host": "127.0.0.1",
  "database.port": "3306",
  "database.username": databaseUser,
  "database.dbname": databaseName,
};

if (databasePassword) {
  configReplacements["database.password"] = databasePassword;
}

// 用户选定/输入的域名写入 app.base_url
configReplacements["app.base_url"] = appBaseUrl;
configReplacements["app.port"] = servicePort;

logDetail("目标文件: " + configFilePath);
logDetail("替换项: " + JSON.stringify(configReplacements));

var configinstallResult = replace_config_content({
  format: "yaml",
  host_id: hostId,
  filePath: configFilePath,
  replacements: configReplacements,
});

assertActionSuccess(configinstallResult, "replace_config_content");
logInfo("配置文件更新完成，修改项: " + JSON.stringify(configinstallResult.modified_keys));

// ========== [06] 检查并放行服务端口 ==========
logPhase(6, "检查并放行服务端口");

var portRuleCheckResult = port_rule_exists({
  port: servicePort,
});

if (portRuleCheckResult &amp;&amp; portRuleCheckResult.exists) {
  logInfo("端口已放行，跳过: " + servicePort);
} else {
  logInfo("放行端口: " + servicePort);
  set_port_rule({
    port: servicePort,
    brief: "Go: " + hostInfo.host_name,
  });
  delay({ seconds: 2 });
  logDetail("端口规则写入完成，等待 2 秒生效");
}

// ========== [07] 注册并启动 Go 项目 ==========
logPhase(7, "注册并启动 Go 项目");

// 查重：避免重复 create_go_project
var matchedGoProject = findGoProject(goProjectName, hostInfo.host_name, siteRootPath);
var isGoProjectRegistered = !!matchedGoProject;
var goProjectDetail = matchedGoProject;
var activeProjectName = isGoProjectRegistered ?
  resolveGoProjectName(matchedGoProject) :
  goProjectName;
var goProjectCreated = false;

if (!isGoProjectRegistered) {
  logInfo("创建 Go 项目: " + goProjectName + " | 绑定域名: " + bindDomain);

  var createGoProjectResult = create_go_project({
    host_source: "custom",
    host_id: hostId,
    project_name: goProjectName,
    project_exe: binaryFilePath,
    project_cmd: binaryFilePath,
    project_ps: "Go: " + hostInfo.host_name,
    port: String(servicePort),
    path: siteRootPath,
    run_user: "www",
    release_firewall: false,
    is_power_on: true,
    domains: [bindDomain], // 宝塔侧绑定域名
  });

  assertActionSuccess(createGoProjectResult, "create_go_project");
  logDetail("create_go_project 响应: " + JSON.stringify(createGoProjectResult));

  goProjectDetail = verifyGoProjectExists(
    goProjectName,
    hostInfo.host_name,
    siteRootPath,
  );
  activeProjectName = resolveGoProjectName(goProjectDetail) || goProjectName;
  isGoProjectRegistered = true;
  goProjectCreated = true;
  logInfo("Go 项目创建并校验通过");
} else {
  logInfo("Go 项目已存在，跳过创建: " + activeProjectName);
}

// 本地系统绑定域名（lh_hosts.domains），与宝塔侧独立，后端自动去重
var localBindOutcome = bindDomainLocally(hostInfo, bindDomain);
var domainBoundLocally = localBindOutcome.domainBound;

var startGoProjectResult = start_go_project({
  projectName: activeProjectName,
});

if (!startGoProjectResult || startGoProjectResult.status !== true) {
  logInfo(
    "启动失败（" +
    activeProjectName +
    "），重新注册 Go 项目: " +
    goProjectName,
  );
  var recreateGoProjectResult = create_go_project({
    host_source: "custom",
    host_id: hostId,
    project_name: goProjectName,
    project_exe: binaryFilePath,
    project_cmd: binaryFilePath,
    project_ps: "Go: " + hostInfo.host_name,
    port: String(servicePort),
    path: siteRootPath,
    run_user: "www",
    release_firewall: false,
    is_power_on: true,
    domains: [bindDomain],
  });
  assertActionSuccess(recreateGoProjectResult, "create_go_project");
  goProjectDetail = verifyGoProjectExists(
    goProjectName,
    hostInfo.host_name,
    siteRootPath,
  );
  activeProjectName = resolveGoProjectName(goProjectDetail) || goProjectName;
  startGoProjectResult = start_go_project({
    projectName: activeProjectName,
  });
}

assertActionSuccess(startGoProjectResult, "start_go_project");
logDetail("start_go_project 响应: " + JSON.stringify(startGoProjectResult));
logInfo("Go 项目已启动: " + activeProjectName);

// ========== [08] 发送部署完成通知 ==========
logPhase(8, "发送部署完成通知");

var recipientEmail = userProfile.email || "";

// 邮件中域名状态文案
var domainStatusLabel = isExistingDomain ?
  "已有域名" :
  domainBoundLocally ?
  "新绑定" :
  "本地已存在/跳过";

if (recipientEmail) {
  send_email({
    to: recipientEmail,
    subject: "主机搭建通知 - " + hostInfo.host_name,
    body: "&lt;p&gt;主机 &lt;strong&gt;" +
      hostInfo.host_name +
      "&lt;/strong&gt; 工作流执行完成。&lt;/p&gt;" +
      "&lt;ul&gt;" +
      "&lt;li&gt;部署包: " +
      (packageExistsResult.found ? "已存在（跳过上传）" : "已上传") +
      "&lt;/li&gt;" +
      "&lt;li&gt;二进制 " +
      binaryFileName +
      ": " +
      (binaryExistsResult.found &amp;&amp; !shouldExtractPackage ?
        "已存在（未重新解压）" :
        "已解压/更新") +
      "&lt;/li&gt;" +
      "&lt;li&gt;数据库: " +
      databaseName +
      (databaseCreated ? "（新建）" : "（已存在）") +
      "&lt;/li&gt;" +
      "&lt;li&gt;站点域名: " +
      bindDomain +
      "（" +
      appBaseUrl +
      "，" +
      domainStatusLabel +
      "）" +
      "&lt;/li&gt;" +
      "&lt;li&gt;配置文件: " +
      configFilePath +
      " 已更新&lt;/li&gt;" +
      "&lt;li&gt;Go 项目: " +
      activeProjectName +
      "（端口 " +
      servicePort +
      "）&lt;/li&gt;" +
      "&lt;li&gt;Go 项目状态: " +
      (isGoProjectRegistered ? "已注册并已启动" : "未注册") +
      "&lt;/li&gt;" +
      "&lt;/ul&gt;",
  });
  logInfo("通知邮件已发送至: " + recipientEmail);
} else {
  logInfo("用户未配置邮箱，跳过邮件通知");
}

logInfo("工作流全部步骤执行完成");
</code></pre>
<p style=""><strong>如何让用户使用他呢？</strong></p>
<p style="">在基础设施-&gt;功能菜单里 可以添加自定义的菜单，然后绑定工作流</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-hxYA.png&amp;size=m" width="3600px">
</figure>
<p style="">菜单会出现在用户后台的主机详情页面里</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-xIsF.png&amp;size=m">
</figure>
<p style="">用户点一下就能执行了！</p>
<p style=""></p>
<h1 style="" id="%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8A%82%E7%82%B9%E5%87%BD%E6%95%B0">自定义节点函数</h1>
<p style="">我给你们提供了一个查询宝塔应用商店插件的代码，可以参考一下</p>
<pre collapsed="true"><code class="language-js">// ctx.params — 调用方传入的参数
// ctx.api_path / ctx.api_method / ctx.base_url / ctx.timeout_sec
// ctx.admin_token / ctx.user_token（按执行场景注入）

var resp = request({
  url: ctx.api_path,
  method: ctx.api_method || "POST",
  headers: { "Content-Type": "application/json" },
  body: ctx.params,
  timeout: ctx.timeout_sec || 30
});

return handle_response(resp, function(json) {
  if (json.http_status &gt;= 400) {
    return { status: false, code: json.http_status, message: json.msg || "HTTP 请求失败" };
  }
  if (json.code !== undefined &amp;&amp; json.code !== 200) {
    return { status: false, code: json.code, message: json.msg || "业务请求失败" };
  }

  var list = json.list;
  if (!list || !list.data) {
    return { status: false, code: -1, message: "接口返回缺少 list.data" };
  }

  var items = list.data;
  var pageHtml = String(list.page || "");
  var countMatch = pageHtml.match(/class=['"]Pcount['"]\s*&gt;共(\d+)条/i);
  var totalCount = countMatch ? parseInt(countMatch[1], 10) : items.length;

  var names = [];
  for (var i = 0; i &lt; items.length; i++) {
    var item = items[i] || {};
    var label = String(item.title || item.name || "").trim();
    if (label) {
      names.push(label);
    }
  }

  var summary;
  if (totalCount &lt;= 0 || names.length === 0) {
    summary = "共查询到 0 个插件";
  } else {
    summary = "共查询到 " + totalCount + " 个插件，分别是：" + names.join("、");
  }

  return {
    status: true,
    code: 200,
    data: summary
  };
});</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-rzVf.png&amp;size=m" width="2924px">
</figure>
<p style=""></p>
<pre collapsed="true"><code class="language-json">{
  "export_version": 1,
  "exported_at": "2026-05-25",
  "action": {
    "action_key": "get_soft_list",
    "name": "自定义节点 - 应用商店搜索",
    "category": "自定义的二级分类",
    "icon": "lucide:puzzle",
    "description": "筛选宝塔应用商店插件列表",
    "api_path": "/plugin?action=get_soft_list",
    "api_method": "POST",
    "params_schema": [
      {
        "key": "type",
        "type": "string",
        "label": "应用分类",
        "default": 0,
        "required": false,
        "default_type": "string",
        "accepted_types": [
          "string"
        ]
      },
      {
        "key": "query",
        "type": "string",
        "label": "筛选值",
        "required": true,
        "description": "搜索内容",
        "default_type": "string",
        "accepted_types": [
          "string"
        ]
      },
      {
        "key": "p",
        "type": "string",
        "label": "当前页",
        "default": 1,
        "required": false,
        "default_type": "string",
        "accepted_types": [
          "string"
        ]
      },
      {
        "key": "row",
        "type": "string",
        "label": "每页条数",
        "default": 15,
        "required": false,
        "default_type": "string",
        "accepted_types": [
          "string"
        ]
      },
      {
        "key": "force",
        "type": "string",
        "label": "",
        "default": 0,
        "required": false,
        "default_type": "string",
        "accepted_types": [
          "string"
        ]
      }
    ],
    "outputs_schema": [
      {
        "key": "status",
        "type": "bool",
        "label": "是否成功",
        "description": "true 表示成功；失败时为 false"
      },
      {
        "key": "code",
        "type": "int",
        "label": "状态码",
        "description": "成功为 200；HTTP/业务失败时为对应错误码"
      },
      {
        "key": "message",
        "type": "string",
        "label": "错误信息",
        "description": "status=false 时返回，如 HTTP 失败、缺少 list.data"
      },
      {
        "key": "data",
        "type": "object",
        "label": "业务数据",
        "description": "成功时对象，含 list 与 pagination"
      },
      {
        "key": "data.list",
        "type": "array",
        "label": "插件列表",
        "description": "应用商店条目数组（btwaf、bt_report 等）"
      },
      {
        "key": "data.pagination",
        "type": "object",
        "label": "分页信息",
        "description": "由 list.page HTML 解析得到"
      },
      {
        "key": "data.pagination.currentPage",
        "type": "int",
        "label": "当前页",
        "description": "对应当前页码，如 1"
      },
      {
        "key": "data.pagination.totalPages",
        "type": "int",
        "label": "总页数",
        "description": "如 1/1 中的 1"
      },
      {
        "key": "data.pagination.totalCount",
        "type": "int",
        "label": "总条数",
        "description": "如「共5条」中的 5"
      },
      {
        "key": "data.pagination.rangeFrom",
        "type": "int",
        "label": "本页起始序号",
        "description": "如「从1-5条」中的 1"
      },
      {
        "key": "data.pagination.rangeTo",
        "type": "int",
        "label": "本页结束序号",
        "description": "如「从1-5条」中的 5"
      },
      {
        "key": "data.pagination.pageHtml",
        "type": "string",
        "label": "分页原始HTML",
        "description": "宝塔返回的 list.page 原文"
      }
    ],
    "response_mapping": [],
    "script": "// ctx.params — 调用方传入的参数\n// ctx.api_path / ctx.api_method / ctx.base_url / ctx.timeout_sec\n// ctx.admin_token / ctx.user_token（按执行场景注入）\n\nvar resp = request({\n  url: ctx.api_path,\n  method: ctx.api_method || \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: ctx.params,\n  timeout: ctx.timeout_sec || 30\n});\n\nreturn handle_response(resp, function(json) {\n  if (json.http_status \u003e= 400) {\n    return { status: false, code: json.http_status, message: json.msg || \"HTTP 请求失败\" };\n  }\n  if (json.code !== undefined \u0026\u0026 json.code !== 200) {\n    return { status: false, code: json.code, message: json.msg || \"业务请求失败\" };\n  }\n\n  var list = json.list;\n  if (!list || !list.data) {\n    return { status: false, code: -1, message: \"接口返回缺少 list.data\" };\n  }\n\n  var items = list.data;\n  var pageHtml = String(list.page || \"\");\n  var countMatch = pageHtml.match(/class=['\"]Pcount['\"]\\s*\u003e共(\\d+)条/i);\n  var totalCount = countMatch ? parseInt(countMatch[1], 10) : items.length;\n\n  var names = [];\n  for (var i = 0; i \u003c items.length; i++) {\n    var item = items[i] || {};\n    var label = String(item.title || item.name || \"\").trim();\n    if (label) {\n      names.push(label);\n    }\n  }\n\n  var summary;\n  if (totalCount \u003c= 0 || names.length === 0) {\n    summary = \"共查询到 0 个插件\";\n  } else {\n    summary = \"共查询到 \" + totalCount + \" 个插件，分别是：\" + names.join(\"、\");\n  }\n\n  return {\n    status: true,\n    code: 200,\n    data: summary\n  };\n});",
    "timeout_sec": 30,
    "status": 1,
    "sort": 0,
    "scope": "admin",
    "allow_external_url": 1
  }
}</code></pre>
<p style="">直接复制这段 JSON 保存到</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-qzEV.png&amp;size=m" width="2496px">
</figure>
<p style="">然后在工作流的这里就能看到和使用了</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-odvu.png&amp;size=m" width="758px">
</figure>
<p style=""></p>
<p style="">效果：</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-eTRA.png&amp;size=m" width="1830px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-RLoU.png&amp;size=m" width="1680px">
</figure>
<p style=""></p>
<p style="">如果你还有想调用查询本地 SQL 的需求，有的有的</p>
<p style=""></p>
<p style="">我加了执行 SQL 的节点函数，支持增删改查，有 SQL 注入攻击防护，支持执行多行 SQL。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-tlTV.png&amp;size=m">
</figure>
<h1 style="" id="%E6%AD%A3%E5%BC%8F%E6%B5%8B%E8%AF%95">正式测试</h1>
<p style="">先创建一个 Go 类型的主机</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-hoTM.png&amp;size=m" width="2096px">
</figure>
<p style=""></p>
<p style="">试一下一键部署！</p>
<p style=""></p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-RKBv.png&amp;size=m">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-ZiCR.png&amp;size=m" width="2436px">
</figure>
<p style=""></p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-wAyT.png&amp;size=m">
</figure>
<p style="">带有实时进度！</p>
<p style=""></p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-bNhp.png&amp;size=m">
</figure>
<p style=""></p>
<p style="">完美了！</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-cenm.png&amp;size=m" width="2018px">
</figure>
<p style="">剩下的就靠你们自己发挥创造力了！</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-LnOQ.png&amp;size=m" width="2074px">
</figure>
<p style="">后面会更新一个脚本商店的功能，有可能实现站长盈利！</p>
<p style=""></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019e5edc-e47a-74cb-8bbc-d28173287ced</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-hpwv.png&amp;size=m" type="image/jpeg" length="95131"/><pubDate>Mon, 25 May 2026 11:38:33 GMT</pubDate></item><item><title><![CDATA[小氢云商城系统 v2.2.7 升级通知]]></title><link>https://bbs.ithxs.com/archives/019e2d6f-c7c7-72af-86b6-6daeaac0ac35</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%95%86%E5%9F%8E%E7%B3%BB%E7%BB%9F%20v2.2.7%20%E5%8D%87%E7%BA%A7%E9%80%9A%E7%9F%A5&amp;url=/archives/019e2d6f-c7c7-72af-86b6-6daeaac0ac35" width="1" height="1" alt="" style="opacity:0;">
<p style="">新增授权密钥弹窗增加节点切换下拉框，可在填写密钥前切换授权节点</p>
<p style="">新增幸运抽奖功能（大转盘）</p>
<p style="">新增管理后台-抽奖活动管理（活动CRUD、奖品配置、中奖记录）</p>
<p style="">新增用户中心-幸运抽奖页面（大转盘抽奖、中奖记录）</p>
<p style="">新增实物奖品发货流程（用户领取→待发货→管理员发货→已发货+物流信息）</p>
<p style="">新增抽奖活动管理页增加「说明」按钮和使用教程弹窗</p>
<p style="">新增抽奖奖品新增「商品兑换卡」类型（自动生成卡密+中奖弹窗显示+复制）</p>
<p style="">新增管理后台-系统设置增加卡密生成权限价格配置项</p>
<p style="">新增用户中心-卡密生成权限购买增加价格显示和确认对话框</p>
<p style="">新增后台新增 /api/admin/updateUserPermissions 独立接口（不校验密码）</p>
<p style="">新增商品编辑增加「活动余额下单」开关</p>
<p style="">新增活动余额支付功能（PaymentWay返回选项+下单接口支持）</p>
<p style="">新增PC端商品详情-立即购买传pt参数到PaymentWay</p>
<p style="">新增PE端首页购买流程从旧main.php切到新/api/PaymentWay接口并传pt参数</p>
<p style="">新增后台管理-文件管理解压增加弹窗交互（目录浏览器选择、输入解压密码）</p>
<p style="">新增文件管理-目录大小列增加"计算"按钮可统计目录内文件总大小</p>
<p style="">新增后台管理-文件管理增加压缩功能（可选择ZIP/TAR.GZ/TAR格式、设置密码、自定义文件名）</p>
<p style="">新增管理后台API调用助手脚本（admin_api.sh / get_admin_token.sh）</p>
<p style="">修复用户中心-/api/getUserInfo 缺少 OptionalAuth 中间件导致 token 未解析、前端误判为未登录清空 token</p>
<p style="">修复修复 GetSubstationConfig 未返回 card_create_price 导致用户端始终显示免费</p>
<p style="">修复新订单消息盒子里「下单用户」显示游客问题修复（已登录用户自动补全用户名）</p>
<p style="">优化PE/PC 订单详情页渲染 custom_content（供应商订单进度自定义数据）</p>
<p style="">优化用户管理-卡密生成权限从编辑表单独立为表格开关列</p>
<p style="">优化用户管理-操作菜单编辑按钮补全缺失的图标</p>
<p style="">优化管理后台-编辑用户密码不再必填，留空则不修改</p>
<p style="">优化路由管理-解压文件弹窗增加密码输入和目录选择功能</p>
<p style="">优化文件管理器增加拖拽框选功能（卡片和列表视图均支持）</p>
<p style="">优化文件管理-列表/卡片切换使用 v-show 保留 DOM 状态避免重建</p>
<p style="">优化文件管理-解压时目标目录/文件冲突自动追加" - 副本"重命名避免覆盖</p>
<p style="">优化features/cloud_config缓存TTL与license缓存保持一致，防止features提前过期导致功能误判（主机系统"未授权"）</p>]]></description><guid isPermaLink="false">/archives/019e2d6f-c7c7-72af-86b6-6daeaac0ac35</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth-xeAv.jpg&amp;size=m" type="image/jpeg" length="46605"/><category>小氢云商城系统</category><category>小氢云商城系统更新记录</category><pubDate>Fri, 15 May 2026 21:00:06 GMT</pubDate></item><item><title><![CDATA[小氢云聚合登录系统]]></title><link>https://bbs.ithxs.com/archives/019e034b-9d8d-716b-b988-bb0116b1b86e</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E8%81%9A%E5%90%88%E7%99%BB%E5%BD%95%E7%B3%BB%E7%BB%9F&amp;url=/archives/019e034b-9d8d-716b-b988-bb0116b1b86e" width="1" height="1" alt="" style="opacity:0;">
<div class="markdown-edited">
 <h1 id="聚合登录云平台一站式-oauth-授权解决方案">聚合登录云平台：一站式 OAuth 授权解决方案</h1>
 <h2 id="背景">背景</h2>
 <p>在当今的互联网生态中，第三方登录已经成为应用的标配功能。QQ、微信、支付宝等平台的用户基数庞大，接入这些登录方式可以显著降低用户的注册门槛，提升转化率。</p>
 <p>然而，逐个对接不同的 OAuth 服务商，意味着要处理各平台差异化的协议实现、回调校验、Token 管理，开发和维护成本并不低。</p>
 <p><strong>聚合登录云平台</strong>就是为了解决这个问题而生的。</p>
 <hr>
 <h2 id="什么是聚合登录">什么是聚合登录？</h2>
 <p>聚合登录云平台是一个<strong>标准化的 OAuth 2.0 授权中间件</strong>，它位于你的应用和第三方登录平台之间：</p>
 <ul>
  <li>你的应用只需对接聚合登录的统一接口</li>
  <li>聚合登录负责对接 QQ、微信、支付宝等平台</li>
  <li>一次集成，多处使用</li>
 </ul>
 <p>从用户视角来看，聚合登录是完全透明的 —— 用户在你的应用上点击「QQ 登录」，跳转授权，回跳到你的应用，整个过程和直连 QQ 登录没有任何区别。</p>
 <hr>
 <h2 id="核心功能">核心功能</h2>
 <h3 id="多平台聚合">多平台聚合</h3>
 <p>聚合登录已支持 QQ 登录，微信、支付宝等更多平台持续接入中。所有平台使用<strong>同一套 OAuth 2.0 协议接口</strong>，你的应用无需关心各平台的协议差异。</p>
 <pre><code>你的应用 → 聚合登录 API → QQ / 微信 / 支付宝
         ← 统一授权回调 ←
</code></pre>
 <h3 id="灵活的套餐体系">灵活的套餐体系</h3>
 <p>不同规模的应用需要不同的资源配置。聚合登录提供多档套餐：</p>
 <ul>
  <li><strong>免费套餐</strong>：适合个人开发测试</li>
  <li><strong>进阶套餐</strong>：适合中小企业生产环境</li>
  <li><strong>高级套餐</strong>：适合高并发业务场景</li>
 </ul>
 <p>所有套餐的用户数、API 速率限制清晰透明，支持<strong>随时升级</strong>，即时生效。</p>
 <h3 id="安全风控">安全风控</h3>
 <p>安全是身份认证服务的生命线。聚合登录提供多层次安全保障：</p>
 <ul>
  <li><strong>应用审核机制</strong>：新创建的应用需经管理员审核通过后方可对外使用</li>
  <li><strong>回调域名校验</strong>：严格验证回调域名，防止授权码被劫持</li>
  <li><strong>回调接口校验</strong>：可选配置精确的回调 URL，双重保险</li>
  <li><strong>密钥加密存储</strong>：应用密钥加密后存入数据库</li>
  <li><strong>CSRF 防护</strong>：标准 OAuth 2.0 state 参数，防止跨站请求伪造</li>
 </ul>
 <h3 id="实时统计">实时统计</h3>
 <p>控制台提供完整的统计功能：</p>
 <ul>
  <li>应用总用户数、活跃应用数一目了然</li>
  <li>7 天趋势图表：新建应用数、成功登录数</li>
  <li>详细登录日志，支持按时间、用户筛选</li>
 </ul>
 <h3 id="极速接入">极速接入</h3>
 <p>从注册到上线，仅需三个步骤：</p>
 <ol>
  <li><strong>注册开发者账号</strong> — 创建账号，进入控制台</li>
  <li><strong>创建应用</strong> — 填写应用信息，选择套餐，提交审核</li>
  <li><strong>集成上线</strong> — 参考对接文档完成 OAuth 流程集成</li>
 </ol>
 <hr>
 <h2 id="典型案例">典型案例</h2>
 <h3 id="场景一个人博客接入-qq-登录">场景一：个人博客接入 QQ 登录</h3>
 <p>个人博客运营者小王，希望在博客上添加「QQ 登录」功能，让读者可以一键评论互动。</p>
 <p><strong>传统方式</strong>：需要去 QQ 互联申请 AppID、配置回调地址、编写 OAuth 交互逻辑、处理 Token 管理。</p>
 <p><strong>使用聚合登录</strong>：注册账号 → 创建应用 → 复制文档中的代码片段到博客项目中 → 完成。整个过程不到 10 分钟。</p>
 <h3 id="场景二saas-平台多租户登录">场景二：SaaS 平台多租户登录</h3>
 <p>某 SaaS 服务商有多个客户，每个客户都需要独立的登录集成。</p>
 <p>聚合登录的<strong>多应用管理</strong>功能让服务商可以为每个客户创建独立的应用，各自配置回调域名和套餐，互不干扰。</p>
 <hr>
 <h2 id="技术架构">技术架构</h2>
 <p>聚合登录云平台基于以下技术栈构建：</p>
 <table>
  <thead>
   <tr>
    <th>层级</th>
    <th>技术</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td>后端框架</td>
    <td>Go + Fiber</td>
   </tr>
   <tr>
    <td>数据库</td>
    <td>PostgreSQL</td>
   </tr>
   <tr>
    <td>缓存</td>
    <td>Redis</td>
   </tr>
   <tr>
    <td>前端</td>
    <td>Vue 3 + Ant Design Vue</td>
   </tr>
   <tr>
    <td>协议</td>
    <td>OAuth 2.0 (RFC 6749)</td>
   </tr>
  </tbody>
 </table>
 <p>高性能、低延迟，Redis 缓存加速关键数据访问，毫秒级响应。</p>
 <hr>
 <h2 id="快速开始">快速开始</h2>
 <p>访问 <strong><a href="https://login.ithxs.com">login.ithxs.com</a></strong> 注册账号，立即体验。</p>
 <h3 id="1-注册账号">1. 注册账号</h3>
 <p>打开官网，点击「免费注册」，填写基本信息即可完成注册。</p>
 <h3 id="2-创建应用">2. 创建应用</h3>
 <p>进入控制台，点击「创建应用」，填写：</p>
 <ul>
  <li>应用名称</li>
  <li>回调域名（如 <code>example.com</code>）</li>
  <li>回调接口（可选，如 <code>https://example.com/oauth/callback</code>）</li>
  <li>选择套餐</li>
 </ul>
 <p>提交后等待管理员审核。</p>
 <h3 id="3-集成-oauth">3. 集成 OAuth</h3>
 <p>应用审核通过后，你将获得 <code>APP ID</code> 和 <code>APP KEY</code>。</p>
 <p><strong>前端跳转登录：</strong></p>
 <pre><code>GET /oauth2.0/authorize?response_type=code
  &amp;client_id=YOUR_APP_ID
  &amp;redirect_uri=YOUR_CALLBACK_URL
  &amp;state=YOUR_STATE
</code></pre>
 <p><strong>后端换取 Token：</strong></p>
 <pre><code>GET /oauth2.0/token?grant_type=authorization_code
  &amp;client_id=YOUR_APP_ID
  &amp;client_secret=YOUR_APP_KEY
  &amp;code={code}
  &amp;redirect_uri=YOUR_CALLBACK_URL
</code></pre>
 <p><strong>获取用户信息：</strong></p>
 <pre><code>GET /oauth2.0/me?access_token={token}
</code></pre>
 <hr>
 <h2 id="结语">结语</h2>
 <p>聚合登录云平台的定位很简单：<strong>让你以最小的代价获得专业的 OAuth 授权能力</strong>。</p>
 <p>无论是个人项目还是商业应用，聚合登录都能为你省去对接各平台的繁琐工作，让你专注于核心业务开发。</p>
 <p>立即体验：<a href="https://login.ithxs.com">login.ithxs.com</a></p>
 <hr>
 <p><em>如有任何问题或建议，欢迎随时联系。</em></p>
</div>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019e034b-9d8d-716b-b988-bb0116b1b86e</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth.jpg&amp;size=m" type="image/jpeg" length="75997"/><pubDate>Thu, 7 May 2026 16:42:25 GMT</pubDate></item><item><title><![CDATA[小氢云系列系统演示站]]></title><link>https://bbs.ithxs.com/archives/019de244-0c06-745e-bb13-e0a2fec1cf06</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E7%B3%BB%E5%88%97%E7%B3%BB%E7%BB%9F%E6%BC%94%E7%A4%BA%E7%AB%99&amp;url=/archives/019de244-0c06-745e-bb13-e0a2fec1cf06" width="1" height="1" alt="" style="opacity:0;">
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%B9%BF%E5%91%8A%E6%8A%95%E6%94%BE%E7%B3%BB%E7%BB%9F">小氢云广告投放系统</h3>
<p style="">演示站地址：<a href="https://demo-ad.ithxs.com" target="_blank" rel="">https://demo-ad.ithxs.com</a></p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E6%8E%88%E6%9D%83%E7%B3%BB%E7%BB%9F">小氢云授权系统</h3>
<p style=""><a href="https://demo-auth.ithxs.com" target="_blank" rel="">https://demo-auth.ithxs.com</a></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%95%86%E5%9F%8E%E7%B3%BB%E7%BB%9F">小氢云商城系统</h3>
<p style=""><a href="https://demo-xqy.ithxs.com" target="_blank" rel="">https://demo-xqy.ithxs.com</a></p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E6%8A%A4%E8%88%AA%E7%B3%BB%E7%BB%9F">小氢云护航系统</h3>
<p style=""><a href="https://demo-game.ithxs.com" target="_blank" rel="">https://demo-game.ithxs.com</a></p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%8F%91%E5%8D%A1%E7%B3%BB%E7%BB%9F">小氢云发卡系统</h3>
<p style=""><a href="https://demo-faka.ithxs.com" target="_blank" rel="">https://demo-faka.ithxs.com</a></p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E6%94%AF%E4%BB%98%E5%AE%9D%E4%B8%AD%E5%8F%B0">小氢云支付宝中台</h3>
<p style="">待添加</p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E7%B3%BB%E7%BB%9F%E5%88%86%E6%9E%90%E7%B3%BB%E7%BB%9F">小氢云系统分析系统</h3>
<p style="">待添加</p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E8%81%9A%E5%90%88-api-%E7%B3%BB%E7%BB%9F">小氢云聚合 API 系统</h3>
<p style="">待添加</p>
<p style=""></p>
<h3 style="" id="%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%AF%BC%E8%88%AA%E7%B3%BB%E7%BB%9F">小氢云导航系统</h3>
<p style=""><a href="https://demo-saas.ithxs.com" target="_blank" rel="">https://demo-saas.ithxs.com</a></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019de244-0c06-745e-bb13-e0a2fec1cf06</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F0072Vf1pgy1foxlo91kiwj31hc0u0k9u.jpg&amp;size=m" type="image/jpeg" length="72110"/><pubDate>Fri, 1 May 2026 06:42:44 GMT</pubDate></item><item><title><![CDATA[小氢云商城系统 v2.2.3 升级通知]]></title><link>https://bbs.ithxs.com/archives/c25f44b1-7c3f-4495-9f69-b537d8580336</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E5%95%86%E5%9F%8E%E7%B3%BB%E7%BB%9F%20v2.2.3%20%E5%8D%87%E7%BA%A7%E9%80%9A%E7%9F%A5&amp;url=/archives/c25f44b1-7c3f-4495-9f69-b537d8580336" width="1" height="1" alt="" style="opacity:0;">
<h3 style="" id="%E6%96%B0%E5%A2%9E%E5%8A%9F%E8%83%BD--%E7%94%A8%E6%88%B7%E7%AB%AF%E3%80%8C%E6%88%91%E7%9A%84%E8%AE%A2%E5%8D%95%E3%80%8D%E6%B8%B8%E5%AE%A2%E6%9F%A5%E5%8D%95%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%A2%9E%E5%8A%A0%E4%BF%A1%E6%81%AF%E8%A7%A3%E7%A0%81%E6%8C%89%E9%92%AE%E4%B8%8E%E5%BC%B9%E7%AA%97%EF%BC%8C%E5%B0%81%E8%A3%85-guestorderreveal%EF%BC%8C%E8%A7%A3%E7%A0%81%E5%90%8E%E5%90%88%E5%B9%B6%E5%88%97%E8%A1%A8%E4%B8%8E%E8%AF%A6%E6%83%85%E4%B8%AD%E7%9A%84%E4%B8%8B%E5%8D%95%E4%BF%A1%E6%81%AF%E4%B8%8E%E5%8D%A1%E5%AF%86%EF%BC%88%E4%B8%8E-pc%E7%AB%AF%E8%A1%8C%E4%B8%BA%E4%B8%80%E8%87%B4%EF%BC%89%E3%80%82">新增功能- <strong>用户端「我的订单」游客查单模式</strong>：增加信息解码按钮与弹窗，封装 <code>guestOrderReveal</code>，解码后合并列表与详情中的下单信息与卡密（与 PC端行为一致）。</h3>
<ul>
 <li>
  <p style=""><strong>管理后台系统设置 -通知推送 -语音通知</strong>：支持按交单状态多选、自定义 MP3上传（地址栏内置上传功能）；消息中心订单推送携带 <code>order_code</code>并按配置播放；<code>image_up</code>支持上传 MP3并存入 <code>static/media</code>目录。</p>
 </li>
 <li>
  <p style=""><strong>订单详情类接口新增 </strong><code>custom_content</code><strong>字段</strong>：包含 <code>label</code>与 <code>value</code>，数据源为订单 <code>data.custom_content</code>；涉及接口：<code>/api/getOrderDetail</code>、<code>/api/queryOrder</code>、<code>/api/orderByKey</code>。游客模式下，对敏感字段（<code>value</code>、<code>tokens</code>、<code>redeem_links</code>、备注发卡段）脱敏逻辑保持一致，并受 <code>order_flow_display</code>控制进度展示；PC与 PE端未登录订单页统一支持信息解码与回显；PE端订单列表与详情页未登录及查单同样支持信息解码；<code>normalizeQueryOrderItem</code>补充 <code>uid</code>字段。</p>
 </li>
 <li>
  <p style=""><strong>管理后台操作密钥（</strong><code>security.admin_operation_key</code><strong> / </strong><code>SECURITY_ADMIN_OPERATION_KEY</code><strong>）</strong>：支持通过请求头 <code>X-Admin-Operation-Key</code>或查询参数 <code>admin_operation_key</code>传递。密钥与配置一致时，等同主管理员 JWT，可访问需鉴权的接口，并绕过云端授权、免责声明与数据库安全口令限制；<code>config.yaml</code>新增示例配置项。</p>
 </li>
 <li>
  <p style=""><strong>数据库安全相关接口与页面（数据迁移、备份、管理）</strong>：引入独立安全口令（<code>sky_config</code>中存储 bcrypt加密值），首次进入后台强制设置，修改需提供旧密码；签发短时 JWT（30分钟有效，通过 <code>X-Database-Safe-Token</code>传递）；未验证接口返回 <code>code=94001</code>，前端弹窗提示解锁；安全口令表单 <code>maxlength</code>绑定数字类型，消除类型告警；<code>Verify/set-password</code>接口支持主站管理员 JWT（<code>UserID=0</code>）下的口令判定及短时 token返回；<code>DatabaseSafeGate</code>组件修复首次设置表单未绑定模型与 <code>@finish</code>问题。</p>
 </li>
 <li>
  <p style=""><strong>后台商品编辑新增“每份发货数量” (</strong><code>delivery_quantity</code><strong>)</strong>：当 <code>price_precision</code>不在2~8有效范围内时，按售价字符串自动推断精度（管理员与供货商共用逻辑）。</p>
 </li>
 <li>
  <p style=""><strong>后台卡密批量导入支持“去重添加”</strong>：弹窗与后端 <code>batchImport</code>逻辑保持一致，在同一商品、同一规格的本次导入批次内自动去重。</p>
 </li>
 <li>
  <p style=""><strong>游客查单密码功能</strong>：下单/详情页支持选填密码，密码使用 bcrypt存储，接口校验后完整回显。</p>
 </li>
</ul>
<h3 style="" id="%E9%97%AE%E9%A2%98%E4%BF%AE%E5%A4%8D--%E7%AE%A1%E7%90%86%E7%AB%AF%E6%9C%AC%E6%9C%BA%E8%AF%AD%E9%9F%B3%E6%8F%90%E9%86%92%EF%BC%9A%E4%BF%AE%E5%A4%8D%E9%A6%96%E6%AC%A1%E7%82%B9%E5%87%BB%E9%A1%B5%E9%9D%A2%E6%88%96%E6%B6%88%E6%81%AF%E9%93%83%E9%93%9B%E6%97%B6%EF%BC%8C%E6%9C%AA%E8%83%BD%E8%A7%A3%E9%94%81%E9%9F%B3%E9%A2%91%E4%BB%A5%E7%AC%A6%E5%90%88%E6%B5%8F%E8%A7%88%E5%99%A8%E8%87%AA%E5%8A%A8%E6%92%AD%E6%94%BE%E7%AD%96%E7%95%A5%E7%9A%84%E9%97%AE%E9%A2%98%EF%BC%9B%E6%8D%95%E8%8E%B7-notallowederror%E5%BC%82%E5%B8%B8%E6%97%B6%EF%BC%8C%E5%AF%B9%E7%94%A8%E6%88%B7%E8%BF%9B%E8%A1%8C%E4%B8%80%E6%AC%A1%E6%8F%90%E7%A4%BA%E3%80%82">问题修复- <strong>管理端本机语音提醒</strong>：修复首次点击页面或消息铃铛时，未能解锁音频以符合浏览器自动播放策略的问题；捕获 <code>NotAllowedError</code>异常时，对用户进行一次提示。</h3>
<ul>
 <li>
  <p style=""><strong>卡易信货源下单</strong>：修复订单即时返回的卡密仅存卡号与卡密拼接，未包含上游 <code>cardId</code>的问题。</p>
 </li>
 <li>
  <p style=""><strong>系统任务价格监控</strong>：修复支持范围，现在一并兼容网课货源（<code>wk=1</code>，不再排除 <code>aid</code>）。</p>
 </li>
 <li>
  <p style=""><strong>CORS预检</strong>：修复对 <code>X-Database-Safe-Token</code>头部的放行问题，避免浏览器拦截携带数据库安全口令的请求。</p>
 </li>
 <li>
  <p style=""><strong>订单页与游客订单页样式</strong>：修复搜索条样式相互干扰的问题；查单解码密码框样式修正。</p>
 </li>
 <li>
  <p style=""><strong>信息解码弹窗</strong>：修复 <code>zIndex</code>层级问题；详情页 <code>.page</code>的 <code>overflow-x</code>样式裁剪弹层问题。</p>
 </li>
 <li>
  <p style=""><strong>订单详情与列表页文案</strong>：修复卡密、解码、收货优先级、按钮样式与顺序等底部文案的统一性问题。</p>
 </li>
 <li>
  <p style=""><strong>订单详情页布局</strong>：修复购买与价格信息并入信息卡片后的位置优化；卡密区宽度盒模型修正；订单进度条移至卡密区块下方。</p>
 </li>
 <li>
  <p style=""><strong>订单详情金额显示</strong>：修复人民币金额、单价、规格切换等所有金额字段均需按照 <code>price_precision</code>进行舍入的问题。</p>
 </li>
 <li>
  <p style=""><strong>商品详情网课查课按钮</strong>：修复逻辑优化，接口 loading状态优先使用 <code>inject goodsDetailPage</code>。</p>
 </li>
 <li>
  <p style=""><strong>计划任务日志清理</strong>：修复任务逻辑，现在可同时清理磁盘日志；数据库表名统一为 <code>background_process_log</code>，前端新增清理选项。</p>
 </li>
 <li>
  <p style=""><strong>“查看错误”按钮展示逻辑</strong>：修复仅在交单失败且 <code>code=2</code>时展示，避免 <code>code=3</code>被误判。</p>
 </li>
 <li>
  <p style=""><strong>PC端商品详情页</strong>：修复隐藏参数及校验下标不一致的问题，避免已填写项被误报。</p>
 </li>
 <li>
  <p style=""><strong>嵌入静态支付成功页</strong>：修复商品名去除富文本标签后展示，仅保留纯文本内容。</p>
 </li>
</ul>
<h3 style="" id="%E5%8A%9F%E8%83%BD%E4%BC%98%E5%8C%96--%E7%AE%A1%E7%90%86%E7%AB%AF%E6%9C%AC%E6%9C%BA%E8%AF%AD%E9%9F%B3%E6%8F%90%E9%86%92%EF%BC%9A%E5%BC%80%E5%90%AF%E4%B8%94%E6%9C%AA%E5%A1%AB%E5%86%99-mp3%E5%9C%B0%E5%9D%80%E6%97%B6%EF%BC%8C%E9%BB%98%E8%AE%A4%E4%BD%BF%E7%94%A8%E5%86%85%E7%BD%AE-%2Fstatic%2Ftongzhi.mp3%E6%96%87%E4%BB%B6%EF%BC%9Bvite%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E4%BB%A3%E7%90%86-%2Fstatic%E8%B7%AF%E5%BE%84%E8%87%B3%E5%90%8E%E7%AB%AF%E3%80%82">功能优化- <strong>管理端本机语音提醒</strong>：开启且未填写 MP3地址时，默认使用内置 <code>/static/tongzhi.mp3</code>文件；Vite开发环境代理 <code>/static</code>路径至后端。</h3>
<ul>
 <li>
  <p style=""><strong>本机语音提醒并发播放</strong>：优化对短时间内多条 WebSocket订单消息的处理，使用独立 Audio实例并发播放（避免单路互相打断），并发上限为16个。</p>
 </li>
 <li>
  <p style=""><strong>系统设置 -通知推送</strong>：优化管理端本机语音提醒的独立分区展示，与「通知渠道（服务端推送）」的说明进行区分；配置项文案同步优化区分。</p>
 </li>
 <li>
  <p style=""><strong>管理员后台在线客服列表页</strong>：修复左面板点选联系人后，未能清除 URL中残留的 <code>?contactId</code>参数的问题（该参数会导致30s轮询 <code>fetchList</code>时页面回跳或头部弹窗导航意外切换会话）；<code>selectContact</code>逻辑中新增清除 URL <code>contactId</code>的处理；<code>fetchList</code>中类型比较改用 <code>String()</code>，避免类型不一致导致 <code>selectedContact</code>被误清空；URL自动选择逻辑增加 <code>!selectedContact.value</code>防护。</p>
 </li>
 <li>
  <p style=""><strong>PC端装修/首页促销/砍价秒杀等区块</strong>：无数据时不渲染，提升页面性能；<code>AGENTS</code>说明全局规则使用分文件管理。</p>
 </li>
 <li>
  <p style=""><strong>用户端订单列表及详情接口</strong>：优化单选/下拉下单信息的展示，现在显示选项标签，不再显示 <code>value</code>。</p>
 </li>
 <li>
  <p style=""><strong>用户端兑换卡密页</strong>：优化支持说明折叠，未开通状态有相应提示。</p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/c25f44b1-7c3f-4495-9f69-b537d8580336</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fdown-vvdv.jpg&amp;size=m" type="image/jpeg" length="0"/><category>小氢云商城系统</category><category>小氢云</category><pubDate>Tue, 28 Apr 2026 16:25:23 GMT</pubDate></item><item><title><![CDATA[小氢云极曈分析系统安装教程]]></title><link>https://bbs.ithxs.com/archives/019d5824-792e-7149-bdf8-4ad0b9b5de69</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E6%B0%A2%E4%BA%91%E6%9E%81%E6%9B%88%E5%88%86%E6%9E%90%E7%B3%BB%E7%BB%9F%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B&amp;url=/archives/019d5824-792e-7149-bdf8-4ad0b9b5de69" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%AE%89%E8%A3%85-nats">安装 nats</h2>
<p style="">使用宝塔 Docker ，找到 Docker 中的容器，创建容器，使用命令创建，输入下面命令 ：</p>
<pre><code class="language-yaml"># 创建持久化目录
mkdir -p /www/server/nats/jetstream

# 正式部署推荐命令
docker run -d \
  --name jitong-nats \
  --restart unless-stopped \
  -p 4222:4222 \
  -p 8222:8222 \
  -v /www/server/nats/jetstream:/data/jetstream \
  nats:latest \
  -js -sd /data/jetstream</code></pre>
<p style="">端口号可以根据自己的需要更改，改后面的，冒号后面的！</p>
<p style=""></p>
<h2 style="" id="%E5%AE%89%E8%A3%85-postgresql">安装 <code>PostgreSQL</code></h2>
<p style="">继续安装，找到宝塔的应用商店，找到 <code>PostgreSQL</code> 管理器，安装插件，打开插件安装最新版的PostgreSQL 即可。</p>
<p style=""></p>
<p style="">连接你的数据库，使用</p>
<p style=""></p>
<p style=""><code>ALTER USER your_app_user WITH SUPERUSER</code> 命令把你创建的数据库用户改成超级用户，否则用不了！</p>
<p style=""></p>
<h2 style="" id="%E5%AE%89%E8%A3%85-clickhouse">安装 <code>clickhouse</code></h2>
<p style="">再在Docker中安装 <code>clickhouse</code> 直接搜就行</p>
<p style=""></p>
<p style="">安装后打开宝塔终端</p>
<p style=""></p>
<p style="">执行以下命令</p>
<p style=""></p>
<p style="">1️⃣ 进入 ClickHouse 容器</p>
<p style=""><code>docker exec -it 你的容器名 bash</code></p>
<p style=""></p>
<h3 style="" id="2%EF%B8%8F%E2%83%A3-%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E6%96%B0%E7%9A%84%E7%94%A8%E6%88%B7%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6">2️⃣ 创建一个新的用户配置文件</h3>
<p style="">在 <code>/etc/clickhouse-server/users.d/</code> 目录下新建一个 <code>jitong.xml</code>：</p>
<p style=""><span style="color: var(--dsw-alias-label-primary); font-family: var(--ds-font-family-code); font-size: 12px; line-height: 18px">bash</span></p>
<pre><code class="language-yaml">cat &gt; /etc/clickhouse-server/users.d/jitong.xml &lt;&lt; 'EOF'
&lt;yandex&gt;
    &lt;users&gt;
        &lt;jitong&gt;
            &lt;password&gt;你的密码&lt;/password&gt;
            &lt;networks&gt;
                &lt;ip&gt;::/0&lt;/ip&gt;
            &lt;/networks&gt;
            &lt;profile&gt;default&lt;/profile&gt;
            &lt;quota&gt;default&lt;/quota&gt;
        &lt;/jitong&gt;
    &lt;/users&gt;
&lt;/yandex&gt;
EOF</code></pre>
<blockquote>
 <p style="">说明：</p>
 <ul>
  <li>
   <p style=""><code>password</code> 标签内是明文密码 <code>**********</code>（你也可以用 <code>&lt;password_sha256_hex&gt;</code> 加 hash，但明文简单）。</p>
  </li>
  <li>
   <p style=""><code>&lt;ip&gt;::/0&lt;/ip&gt;</code> 表示允许任何 IP 连接，按需可改成具体 IP 段。</p>
  </li>
  <li>
   <p style=""><code>profile</code> 和 <code>quota</code> 使用默认值。</p>
  </li>
 </ul>
</blockquote>
<h3 style="" id="3%EF%B8%8F%E2%83%A3-%E9%80%80%E5%87%BA%E5%AE%B9%E5%99%A8%E5%B9%B6%E9%87%8D%E5%90%AF-clickhouse">3️⃣ 退出容器并重启 ClickHouse</h3>
<p style=""></p>
<p style=""><code>exit</code></p>
<p style=""></p>
<p style="">重启容器</p>
<p style=""><code>docker restart 容器名</code></p>
<h3 style="" id="4%EF%B8%8F%E2%83%A3-%E9%AA%8C%E8%AF%81%E7%94%A8%E6%88%B7%E6%98%AF%E5%90%A6%E5%88%9B%E5%BB%BA%E6%88%90%E5%8A%9F">4️⃣ 验证用户是否创建成功</h3>
<p style=""></p>
<p style=""><code>docker exec 容器名 clickhouse-client -u jitong --password 你的密码 -q "SELECT 1"</code></p>
<p style=""></p>
<p style="">如果返回 <code>1</code>，说明用户创建成功且密码正确。</p>
<p style=""></p>
<p style=""></p>
<h2 style="" id="%E6%B3%A8%E6%84%8F">注意</h2>
<p style="">使用 Docker 安装的话配置文件里填写的地址就尽量填写 Docker 的内网地址，如：<span style="background-color: rgb(233, 246, 235); color: rgb(32, 165, 58); font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, Arial, &quot;sans-serif&quot;; font-size: 12px">172.17.0.3</span></p>
<p style=""></p>
<p style="">建议使用 Docker 内网地址！</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%259B%25BE%25E7%2589%2587-ssjZ.png&amp;size=m" width="2122px">
</figure>
<p style=""></p>
<h2 style="" id="%E5%85%B6%E4%BB%96">其他</h2>
<p style="">剩下的安装和商城逻辑一样！</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019d5824-792e-7149-bdf8-4ad0b9b5de69</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fdown-CvUL.jpg&amp;size=m" type="image/jpeg" length="0"/><category>小氢云</category><pubDate>Thu, 23 Apr 2026 16:59:55 GMT</pubDate></item><item><title><![CDATA[关于最近群里有人抹黑的事]]></title><link>https://bbs.ithxs.com/archives/019d5bf2-cc56-7254-abe3-511fa9861100</link><description><![CDATA[<img src="https://bbs.ithxs.com/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8E%E6%9C%80%E8%BF%91%E7%BE%A4%E9%87%8C%E6%9C%89%E4%BA%BA%E6%8A%B9%E9%BB%91%E7%9A%84%E4%BA%8B&amp;url=/archives/019d5bf2-cc56-7254-abe3-511fa9861100" width="1" height="1" alt="" style="opacity:0;">
<p style="">事情起因是这样，群里有一些人整天发广告，就撤回+禁言套餐了，刚好有这个人，一直发消息问为啥禁言他，看这架势就懒得回 没理他，不知道是不是破防了就开始喷了，小学生行为懒得理，最近更是放肆了，到处发抹黑消息😂</p>
<p style="">估计不少人被这狗咬过</p>
<p style="">下面是截图：</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1d3791ab239b57b3dbe8ffccf5161d0c.png&amp;size=m" width="1320px">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F4511af976ecb4d3781706b0fe242ba21.png&amp;size=m" width="1320px">
</figure>
<p style="">😂</p>
<p style="">真的不想回这种问题</p>]]></description><guid isPermaLink="false">/archives/019d5bf2-cc56-7254-abe3-511fa9861100</guid><dc:creator>Aツ韩先生</dc:creator><enclosure url="https://bbs.ithxs.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F0072Vf1pgy1foxlo91kiwj31hc0u0k9u.jpg&amp;size=m" type="image/jpeg" length="72110"/><pubDate>Sun, 5 Apr 2026 04:47:24 GMT</pubDate></item></channel></rss>