🚀 入门指南 3
📘 IDC插件开发 10
🖥️ KVM受控端 2
🔌 API接口文档 1
📝 更新日志 2
一、 一、系统介绍

语信IDC财务管理系统是一款基于ThinkPHP 8开发的IDC财务管理系统,支持虚拟主机、VPS/云服务器、CDN等产品的销售和管理。系统采用插件化架构,支持通过插件扩展功能,支持多模板切换。

系统特性

  • 多产品类型支持:虚拟主机、VPS/云服务器(弹性购买)、CDN、服务器租用
  • 弹性云重构:VPS 支持弹性规格(CPU/内存/系统盘/带宽/IP 数量)、实时计价、两级联动选系统镜像,购买页按产品类型区分(虚拟主机用 MB,服务器用 GB)
  • 插件化架构:插件必须适配系统(遵循 PluginManager 契约),系统核心永远排在第一位,不因插件改动;支持静态 nodeAction 与实例 hostAction 两种插件风格
  • 多模板支持:支持前台、用户中心模板切换(后台/代理端模板仅官方默认提供),模板安装在 view/template/ 目录,静态资源通过 __STATIC__ 引用
  • 上游分组对接:支持对接第三方财务系统与同系统互联,分组选插件 → 填 API → 测试节点 → 产品联动获取配置
  • 自研 KVM 受控端:kvm-agent 受控端 + kvm 插件,父机 KVM 开出客户 VPS,含云主机控制面板(概览/控制台/电源/重装/重置密码/监控/网络)
  • 虚拟主机控制面板:自研控制面板,支持站点管理、PHP切换、伪静态、SSL等,插件可扩展
  • API对接功能:支持第三方系统通过API对接资源池,实现产品查询、订单创建、主机管理等
  • 同系统对接插件:支持两个语信IDC财务系统之间的资源池对接(A系统对接B系统的产品)

系统要求

  • PHP >= 8.0
  • MySQL >= 5.7
  • ThinkPHP 8.x
  • Web 服务器(Nginx / Apache,运维面板任选,系统本身不绑定特定面板)
文档版本与系统版本:本文档版本号(如 V2.9)只标注文档本身的修订,与系统产品版本(如 V1.3.2.x)不是同一套号。各条契约自对应系统版本起可用,旧版系统若没有某条契约的行为,升级系统后再用;新增契约的引入版本见各章节标注与文末更新日志。

正式版与内测版

语信IDC财务系统采用「正式版 / 内测版」双线发布:

版本线说明发布节奏
正式版稳定版本,只包含已在内测版验证稳定的改动集合每周三打包更新(非紧急情况不追新)
内测版先行版本,包含新功能与修复随改随发
  • 插件 / 模板开发者:联调与适配新契约建议使用内测版(第一时间拿到新行为),面向客户发布内容时以正式版为准;
  • 生产环境用户:使用正式版,非紧急需求不追内测版;
  • 两线版本号各自递增(内测版号可能高于正式版号)。文档版本标注读法:正文标注「自 X 版本起可用 / V1.3.3.5.8 实测」等字样时,一律默认指内测版;正式版当前为 V1.2(尚未追上内测线),正文契约在正式版发布至该版本号前不生效——联调与适配新契约请使用内测版(第一时间拿到新行为),面向客户发布内容时以正式版为准。涉及金额、退款、状态机等关键契约,以「扣款与退款流程」「失败契约」等正文明确标注的版本线为准。
授权站安装部署指南

授权站安装部署指南

环境要求

  • PHP 8.0+(推荐8.2)
  • MySQL 5.7+ / 8.0
  • Nginx / Apache
  • 宝塔面板(推荐)

安装步骤

1. 下载安装包

从官网下载最新安装包,上传到站点根目录,解压。
运行目录必须设为 public/,否则会有安全问题。

2. 运行安装向导

浏览器访问域名,会自动跳转到安装向导页面。
按提示填写:

  • 数据库主机:127.0.0.1
  • 数据库名:你的数据库名
  • 数据库用户:你的数据库用户
  • 数据库密码:你的数据库密码
  • 管理员账号密码

3. 安装完成

安装完成后会自动创建管理员账号,进入后台配置系统。
后台入口:/xiaogui(自定义入口)

注意事项

  • 安装完成后删除 install.lock 文件(或安装向导会自动删除)
  • 运行目录必须设为 public/,否则会有安全问题
  • 需要开启 rewrite 伪静态

Nginx伪静态规则

location / {
    if (!-e ) {
        rewrite  ^(.*)$  /index.php?s=/  last;
    }
}

常见问题

  • 访问404:检查伪静态和运行目录
  • 数据库连接失败:检查数据库配置是否正确
  • 安装页面打不开:检查PHP版本是否>=8.0
二、 二、快速开始
表前缀说明:系统支持自定义表前缀,安装时可填写,默认 yxidc_。系统代码中不写死表前缀,全部使用数据库配置联动。开发插件时请使用 Db::name('表名') 而不是写死表前缀。

目录结构

├── app/                    # 应用目录
│   ├── admin/              # 后台管理
│   ├── home/               # 前台
│   ├── user/               # 用户中心
│   ├── api/                # API接口
│   └── common.php          # 公共函数
├── plugins/                # 插件目录
│   └── plugins/            # 插件列表(纯净安装包为空,每个插件一个目录)
│       └── 插件标识/        # 开发者开发的插件目录
├── view/                   # 模板视图根目录
│   └── template/           # 模板安装目录(V1.1.3.2 起,原根目录 template/ 已迁移)
│       ├── yuxin/          # 官方默认模板
│       │   └── default/
│       └── 开发者标识/模板名/  # 开发者发布的第三方模板
├── public/                 # 公开目录(静态资源发布到 public/static/)
├── route/                  # 路由配置
├── config/                 # 配置文件
│   └── version.php         # 系统版本配置
├── runtime/                # 运行时目录
└── vendor/                 # 第三方库
说明:纯净安装包仅包含系统核心文件和默认模板,不包含任何第三方插件。开发者开发的插件安装后会出现在 plugins/plugins/插件标识/ 目录下。
模板目录迁移说明:V1.1.3.2 起模板目录已从根目录 template/ 迁移到 view/template/(与 ThinkPHP 视图根目录统一)。开发新模板请按新目录结构开发;旧模板通过系统在线更新会自动迁移并清理旧目录。
三、 三、插件开发指南
阅读约定:下文各代码块里的 my_node、my_host_plugin、my_recharge_plugin、my_demo、demo_host、demo_hello 都是独立示例假设的插件标识,彼此不相关,也不对应任何已发布插件;实际开发时统一换成你自己的一个标识(目录名、plugin.json 的 name、命名空间三者一致)。

插件结构

每个插件放在 plugins/plugins/插件标识/ 目录下,包含以下文件:

plugins/plugins/my_host_plugin/
├── My_host_plugin.php # 插件主类(必需,目录名首字母大写、下划线保留)
├── plugin.json        # 插件配置文件(必需)
├── install.sql       # 安装SQL(可选,建插件自带表;写死 yxidc_ 前缀)
├── uninstall.sql     # 卸载SQL(可选,清理插件表)
├── uninstall.php      # 卸载PHP脚本(可选,复杂清理用)
├── config.html        # 可选。注意:系统不读取插件自带 config.html,后台配置页由系统按 plugin.json 的 config_params 统一渲染,本文件可省略
├── controller/       # 控制器目录(可选)
│   ├── User.php       # 用户中心控制器:index 页面 + mount 挂载方法
│   └── Admin.php      # 后台管理控制器(可选,提供后台运营页)
├── view/              # 插件视图(可选)
│   ├── user/         # 用户中心视图(index.html、mount.html 等)
│   ├── admin/        # 后台管理视图(Admin.php 渲染,如 index.html)
│   └── {面板模板}.html # 自定义面板模板按 panel.template 声明名直接放 view 根目录
├── static/           # 插件静态资源(可选,安装时自动发布到 public/static/plugins/插件标识/)
└── README.md          # 插件说明
说明:插件开发遵循「插件适配系统」原则,插件作者署名由作者在 plugin.json 中自行填写(开发者本人 / 工作室名称,系统不强制统一);插件静态资源放在插件目录 static/ 下,系统安装/更新插件时自动发布到 public/static/plugins/插件标识/ 供浏览器访问。插件视图内引用静态资源使用模板替换常量 __STATIC__(全局替换为站点 /static),路径写法如 __STATIC__/plugins/插件标识/css/app.css、__STATIC__/plugins/插件标识/js/app.js,不要写死站点域名或绝对路径。

install.sql 表前缀规则(重要)

插件自带表写 install.sql 时,一律写死系统默认前缀 yxidc_,例如 CREATE TABLE IF NOT EXISTS yxidc_demo_hello_log (...)。系统在安装和卸载时会读取站点实际配置的表前缀,若与 yxidc_ 不同,自动把 SQL 里的 yxidc_ 整体替换成该站点的真实前缀后再执行。

举例:某用户安装时填的表前缀是 abc_,插件 install.sql 里写的是 yxidc_demo_hello_log,系统执行前会自动替换成 abc_demo_hello_log 再建表——不会真的去建一张 yxidc_ 开头的表。所以插件包对所有站点通用,不要在 install.sql 里猜测或填写别的前缀,用户在安装向导里自定义的前缀由系统负责替换。

卸载时系统会依次执行:uninstall.sql(与 install.sql 同样做 yxidc_→实际前缀替换,逐句执行)和 uninstall.php(直接 require 执行,脚本内可使用 \think\facade\Db 做复杂清理)。不需要卸载时这两个文件可不提供。

重装幂等(必读):插件卸载后重新安装时,install.sql 会再次执行——建表请使用 CREATE TABLE IF NOT EXISTS 保证幂等(官方示例已按此写法);如需插入初始化数据,请先 SELECT 判断或用 INSERT IGNORE,避免重装时报「表已存在 / 主键冲突」。

插件更新(覆盖安装)时同样会执行 install.sql(经实测确认):后台「插件管理 → 更新」从授权站下载新包后,系统会先删除插件代码目录、重新解压、然后无条件执行 install.sql。但更新不会删除数据库表——因此 CREATE TABLE IF NOT EXISTS 对已存在的表不会做任何事(新字段加不上)。新增字段 / 变更表结构的插件,必须在启动或 install.sql 后用 information_schema 判重补列(官方示例):

// install.sql 建表后,用 SQL 补列(已存在则跳过)
-- 判断列是否存在,不存在则 ALTER TABLE 添加
SET @col = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{prefix}my_plugin_table' AND COLUMN_NAME = 'new_field');
SET @sql = IF(@col = 0, 'ALTER TABLE {prefix}my_plugin_table ADD COLUMN new_field VARCHAR(64) DEFAULT \'\'', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

({prefix} 为系统替换后的实际表前缀;逐句执行器按分号切句,注意 PREPARE/EXECUTE 需同一批内执行。)

注意:安装侧没有 install.php 钩子——系统安装插件时只解压文件 + 发布 static 静态资源 + 执行 install.sql,不会回调任何安装 PHP 脚本。初始化数据请写在 install.sql 里;复杂初始化逻辑目前不支持,请不要依赖 install.php。
破坏性语句禁令(重要):install.sql 在安装和每次更新(覆盖安装)时都会无条件执行,因此禁止在 install.sql 中写 DROP TABLE、TRUNCATE、DELETE FROM 等破坏性语句——更新时会误删用户线上数据。确需清理旧表 / 废弃数据,一律写在 uninstall.sql(仅卸载时执行),或用 information_schema 判重后做 ALTER 等非破坏变更。

插件后台管理页(可选)

插件若需要在系统后台提供配置或运营页面,在插件目录下提供 controller/Admin.php(命名空间 \plugin\插件标识\controller\Admin)。系统检测到该文件存在后,会在后台「插件管理」列表显示「管理」入口,统一入口地址为:

GET /admin/plugin/manage?name=插件标识&action=方法名
# 或按插件ID:
GET /admin/plugin/manage?id=插件ID&action=方法名

系统会 new Admin()(无参构造)后调用对应方法。方法名不能以下划线开头、须为字母开头的字母数字组合。方法返回 \think\response\View 时,系统自动套用系统后台布局;返回 JSON / 重定向则直接透传。后台管理员身份由系统鉴权,插件控制器内无需再写登录判断。

HTTP 方法与 CSRF:该入口GET 与 POST 均可(路由按任意方法注册),name/id/action 参数 GET、POST 传递均可;系统未启用 CSRF token 校验,插件 AJAX 请求无需携带 token,直接按统一返回口径返回 JSON 即可(code=0 成功 / code=1 失败)。

用户中心插件控制器(controller/User.php)

用户中心插件控制器命名空间 \plugin\插件标识\controller\User,系统以 new User()(无参构造)调用。路由:/user/plugin/插件标识 调 index(),/user/plugin/插件标识/方法名 调对应方法;返回 View 时系统自动套用用户中心布局。当前登录用户 ID 的取法:该控制器不继承系统基类,需自行读取会话 \think\facade\Session::get('user'),其中 ['id'] 即当前登录用户 ID(其余用户资料同数组)。GET 与 POST 两种提交方式均注册可用,系统无 CSRF token 校验,插件方法内用 request() 读取参数即可。

写作防护责任(重要):系统未启用 CSRF token 校验,且用户中心插件控制器不继承系统基类、无登录鉴权兜底(对比:后台插件控制器有管理员身份由系统鉴权兜底;用户中心则完全压在插件自己身上)。因此用户中心插件必须自行校验登录态(Session::get('user') 为空即拒绝),并对写操作(POST)做 CSRF / 来源校验(自行签发校验 token、校验来源地址等)——缺失任一项即存在越权与 CSRF 风险,责任在插件。

类加载与生命周期说明

  • 主类文件固定为 plugins/plugins/插件标识/标识首字母大写.php——只把目录名第一个字符大写,其余字符(含下划线)原样保留。例:目录 my_demo → 主类文件 My_demo.php,命名空间 \plugin\my_demo\My_demo;构造函数接收合并后的配置数组 __construct(array $config)。不要把下划线转驼峰(写成 MyDemo.php 系统会找不到文件)。
  • 子目录类需自行加载:系统只 require 主类文件和显式的 controller/User.php、controller/Admin.php,不会自动加载插件内 model/、lib/ 等子目录里的类。插件内部用到这些类时,请在主类或控制器里自行 require_once。
  • 生命周期钩子暂不提供:后台「启用/停用」只是切换插件状态字段,系统不会回调 onEnable / onDisable 等方法,插件无需也无法依赖这类钩子。
  • 返回口径:主类服务方法返回数组 ['code'=>0|1, 'msg'=>..., 'data'=>...],其中 code=0 表示成功、code=1 表示失败;插件的 controller/Admin.php、controller/User.php 通过 AJAX 返回 JSON 时,与主类保持同一口径(code=0 成功 / code=1 失败,msg 为提示语),前端据此统一判断,不要另造 status/success 等字段。前端弹窗统一使用 window.showToast(消息, 'success'|'error'),不要使用浏览器原生 alert/confirm。

续费与下单定价(财务类 / 立减返现类插件必读)

定价钩子(自 V1.3.3.5 起):系统提供插件参与定价的钩子 pricing()。用户新购下单(用户中心下单 / 开放 API 下单)与续费计算应付金额时,系统会调用插件实例方法(通过 PluginManager::load() 实例化后调用,静态方法声明不会生效,请勿写成 public static function pricing(...)):

/**
 * 定价钩子(可选实现,未实现/异常/返回 code≠0 均跳过,不影响主流程)
 * @param string $scene      场景:new=新购 / renew=续费(仅这两个值;upgrade / terminate 等不经过 pricing,勿写 case 'upgrade' 分支)
 * @param array  $product    产品记录
 * @param array  $period     周期记录(含 price 等)
 * @param float  $baseAmount 基础应付金额(已含用户等级折扣等系统优惠)
 * @param array  $extra      扩展参数(quantity 数量等)
 * @return array ['code'=>0, 'data'=>['discount'=>float 立减金额, 'remark'=>string 优惠说明]]
 */
public function pricing(string $scene, array $product, array $period, float $baseAmount, array $extra = []): array
{
    return ['code' => 0, 'data' => ['discount' => 5.00, 'remark' => '测试立减¥5.00']];
}
计价口径:最终应付 = 基础应付金额 − 立减金额(最低为 0,不会为负)。钩子返回的 remark 会追加到订单备注;仅影响金额,不改变 product_period.price 定价(价格仍由管理员在产品/周期中配置)。插件未实现 pricing() 时系统按原价处理,不影响任何插件。
多插件叠加规则(多个插件同时实现 pricing() 时):系统遍历所有已启用插件逐个调用 pricing(),立减金额累加生效:单个插件的立减上限为「当前剩余应付金额」,所有插件累计立减不超过基础应付(最终应付最低为 0,不会为负);任一插件未实现 / 抛异常 / 返回 code≠0 / 立减金额 ≤ 0 均跳过,不影响其他插件与主流程;每个生效插件的 remark 按调用顺序依次追加进订单备注。

插件仍可通过 hostAction('renew', ['period'=>..., 'setdate'=>...]) 参与续费动作本身(延长到期时间、传周期)。

系统不提供续费成功事件/回调通知。续费成功后系统在以下表留痕,插件可在自身控制器内按需查询(不要依赖实时事件):

系统不提供续费成功事件/回调通知。续费成功后系统在以下表留痕,插件可在自身控制器内按需查询(不要依赖实时事件):

  • order:type='renew' 且 pay_status=1 即续费订单;amount 实付金额、pay_time 支付时间、agent_id 代理ID;
  • service_log:action='renew' 为续费操作留痕,content 含续费后到期信息;
  • finance_log:type='consume' 为续费扣款流水,relate_id 关联订单ID。

财务与余额(财务类 / 返现类插件必读)

系统用户余额与资金流水由核心表统一维护,财务类插件应遵循同一口径,避免账目与系统两张皮:

  • 用户余额字段:user 表的 money(decimal(10,2),账户余额)。插件以 Db::name('user')->where('id',$uid) 读写,不要自建余额表。
  • 资金流水表:finance_log(不带前缀写 Db::name('finance_log')),字段:user_id 用户、type 业务类型(recharge 充值 / consume 消费 / refund 退款 / commission 佣金 / withdraw 提现,可按插件用途扩展)、amount 变动金额(正为入账、负为扣减)、balance 变动后余额、relate_id 关联业务ID(如订单ID,无则 0)、remark 备注、create_time 时间戳。
  • 官方推荐做法:任何一次余额变动都要同事务做两步——先 UPDATE user SET money=money±X,再向 finance_log 插一条流水(balance 写变动后的最新余额)。不要只改余额不记流水。返佣明细记入 commission_log(无独立佣金余额字段,结算后并入 user.money),涉及返佣时一并记录。
  • 与订单关联:系统订单在 order 表,订单明细在 order_item,插件流水的 relate_id 填对应订单ID即可对账。

佣金流水表 commission_log(返佣类插件)

字段说明
agent_id受益代理ID
order_id来源订单ID
user_id归属客户ID
amount佣金金额
rate佣金比例
status结算状态:0=待结算,1=已结算
create_time / update_time创建/更新时间戳
佣金余额口径:系统没有独立的"佣金余额"字段——佣金先以 status=0 待结算 记入 commission_log;后台结算后才并入用户 user.money(同时在 finance_log 记一条 type=commission)。提现走 withdraw 表(status:0待审核/1已通过/2已驳回/3已打款)。返佣类插件不要自建余额字段,按此口径记账即可。

提现申请表 withdraw

字段说明
agent_id申请提现的代理/用户ID
amount提现金额
account收款账号信息
status0=待审核,1=已通过,2=已驳回,3=已打款
audit_time审核时间戳
create_time / update_time创建/更新时间戳
提现与余额关系:用户发起提现时由系统扣减 user.money 并在 finance_log 记一条 type=withdraw 流水;插件如需对账,关联条件是 withdraw.agent_id = finance_log.user_id(withdraw 表只有 agent_id、finance_log 表只有 user_id,两表各持其一,可再按 type=withdraw 过滤),不要自行重复扣余额。

改余额的事务写法(并发安全)

系统官方做法是用数据库事务包住"改余额 + 记流水"两步。下面是一条扣款的自洽示例(防透支 + dec + 负额流水),入账时把 dec 换成 inc、amount 写正、去掉 money 下限条件即可:

\think\facade\Db::startTrans();
try {
    // 1) 先扣余额:money>=扣款额 条件防透支,不足时 update 返回 0 行
    $affected = \think\facade\Db::name('user')
        ->where('id', $uid)
        ->where('money', '>=', $amount)
        ->dec('money', $amount)   // 入账用 inc('money', $amount)
        ->update();
    if (!$affected) {
        \think\facade\Db::rollback();
        return ['code' => 1, 'msg' => '余额不足'];
    }
    // 2) 再记流水,balance 取本次变动后的最新余额
    $balance = (float)\think\facade\Db::name('user')->where('id',$uid)->value('money');
    \think\facade\Db::name('finance_log')->insert([
        'user_id'    => $uid,
        'type'       => 'my_consume', // 插件自定义类型加业务前缀,不要复用系统内置名
        'amount'     => -$amount,     // 金额符号口径:入账存正数、扣减存负数(含符号本身)
        'balance'    => $balance,
        'relate_id'  => $orderId,
        'remark'     => '业务说明',
        'create_time'=> time(),
    ]);
    \think\facade\Db::commit();
} catch (\Throwable $e) {
    \think\facade\Db::rollback();
    throw $e;
}
type 扩展约定:系统内置 recharge/consume/refund/commission/withdraw;插件自定义 type 建议加业务前缀(如 coupon_back、my_*_reward),不要直接复用系统内置名,避免后台按 type 汇总时口径混淆。

plugin.json 配置文件

密码与密钥存储约定(安全)

系统对落库的密码/密钥类字段统一加密存储(AES-256-CBC,密钥取自 .env 的 APP_SECRET_KEY,密文带 enc: 前缀),读取时由系统自动解密(兼容历史明文数据)。涉及以下场景:

  • 服务密码:服务台账的密码字段(如 host.password)由系统在写入时加密(encryptSecret)、读取时解密(decryptSecret)。插件 create 返回的 data.password 由系统统一加密存储,插件无需自行加密;插件在 hostAction 中收到 $host 数组时,其中的密码字段已是系统解密后的明文。
  • API 密钥:用户 API 的 app_secret 加密存储,创建时仅展示一次;授权站对接密钥(auth_app_secret)同样加密。
  • 插件配置:插件 config 字段按插件声明原样保存;如插件自身需要存储敏感配置,可参考系统做法自行加密(系统不强加)。

resetpwd 密码安全边界:系统调用 resetpwd 时生成随机新密码(明文)经 $extra['password'] 传给插件,成功后由系统用 encryptSecret 加密落库(host.password),明文仅回显给用户一次、系统不写日志。插件侧收到明文密码只用于调用上游改密接口,不得自行明文落库、不得写入日志;如需留存应按系统约定加密。

{
    "name": "my_host_plugin",
    "title": "虚拟主机对接插件",
    "version": "1.0.3",
    "author": "your_name",
    "type": "host",
    "description": "对接面板或上游系统,支持虚拟主机自动开通与自研控制面板",
    "config_params": [
        {
            "key": "php_version",
            "name": "PHP版本",
            "type": "select",
            "options": [
                {"value": "", "label": "纯静态"},
                {"value": "74", "label": "PHP 7.4"},
                {"value": "82", "label": "PHP 8.2"}
            ],
            "default": "82",
            "user_selectable": true,
            "dynamic": true,
            "description": "站点运行的PHP版本(从受控端动态获取已安装版本)"
        }
    ],
    "panel": {
        "enabled": true,
        "title": "主机面板",
        "tabs": [
            {"key": "overview", "name": "概览"}
        ]
    }
}

配置字段说明

字段类型说明
namestring插件标识(唯一),同时决定插件安装目录(安装目录 = plugins/plugins/{name}/,目录名、plugin.json 的 name、命名空间三者一致)。插件不需要 developer 字段——developer 是模板 template.json 的约定(模板安装目录按 developer/模板名 分目录),插件安装目录与更新匹配一律由 name 决定
titlestring插件名称
versionstring插件版本
authorstring插件作者署名(开发者本人 / 工作室名称,由发布者自行填写;系统不内置官方作者、不提供默认署名,也不强制统一)
typestring插件分类标识:资源类 host(虚拟主机)/ vps(云服务器)/ cdn(CDN)/ dedi(独立服务器),声明插件主类型:自营模式(插件直接挂载产品)按此过滤可挂载的产品类型;对接类插件(config_mode=group,经上游分组挂载)的服务范围以所属分组的「适用产品类型」为准,type 不构成服务范围限制,仅作标识与自营模式过滤;其余为功能类(如 sms 短信、realname 实名、recharge 充值、marketing 营销、finance 财务、tool 工具等),按插件自身用途填写,系统不做封闭枚举校验(「不做封闭枚举校验」仅表示系统不校验 type 取值,不构成接入授权;type 只是分类标识,服务范围与货源方式由 config_mode / apply_mode 决定——第三方虚拟化平台请按对接类发布)
descriptionstring插件描述
config_paramsarray可配置参数列表
panelobject控制面板声明(enabled/title/template/tabs),所有类型插件需要自建面板时均可声明,数据走 panelApi 分发
user_mountobject用户中心挂载点声明(将插件功能挂载到用户中心对应页面,见下方小节)
config_modestring配置归属模式,可选 global(默认,插件全局配置,在后台「插件管理」点配置填写)或 group(代理/上游插件,配置移到「上游分组」里选择本插件后填写 API 参数,插件管理页不再显示配置按钮)。对接某上游云/面板、需要按上游分组分发时填 group
apply_modeint适用范围,可选 1=仅自营(直售)、2=仅代理分销、3=通用(默认,自营与代理都可用)。缺省按 3 通用处理。字段值与派发矩阵的对应:1=仅能被「自营直挂 / 自营分组」通道选中、2=仅能被「代理插件分组」通道选中、3=两种通道均可选中。apply_mode 只决定该插件能否被某配置通道选中,不决定动作如何派发——动作派发由产品配置通道(plugin_id / 分组 plugin_name)决定,见「动作派发矩阵」
min_versionstring最低兼容系统版本(可选,留空=不限制)。权威位置是开发者中心发布表单:发布时手填优先,本字段(或旧名 require_version)仅在表单留空时作自动回退;两处不一致以发布表单为准

必填性与默认值:其中 name、title、version 为必填,其余均为可选;type 缺省按 host 处理,config_mode 缺省 global,apply_mode 缺省 3。未知字段(包里多写的、系统不认识的字段)系统一律忽略,不报错——插件可自行保留扩展字段供内部读取(min_version / require_version 除外:授权站上架时会读取这两个字段用于兼容闸回退)。

用户中心挂载点(user_mount)

插件需要向用户中心提供功能时,在 plugin.json 中声明 user_mount 挂载点,系统会把插件功能区块直接渲染到对应的用户中心页面内,区块标题直接使用插件名称,不会出现"插件服务/插件"字样的菜单分组。用户中心侧边栏不因插件增加任何菜单项。

挂载点声明

"user_mount": {
    "recharge": {
        "title": "卡密充值",   // 区块标题(直接显示插件名,不要加"插件"字样)
        "icon": "?️",          // 区块图标(可选)
        "action": "mount"      // 插件 User 控制器中的方法名(默认 mount)
    }
}

支持挂载的页面

挂载点 ID对应页面说明
recharge账户充值页 /user/recharge如充值类功能挂载到充值页
finance财务明细页 /user/finance如余额明细、账单类功能
message站内信页 /user/message如消息通知类功能
home用户中心首页 /user/index如个人数据、快捷功能类
扩展性:挂载点 ID 与用户中心控制器一一对应,平台后续新增用户中心页面时同步开放对应挂载点;一个插件可同时挂载多个页面。

插件侧实现

插件提供用户中心控制器 controller/User.php,实现声明的方法(默认 mount),返回视图响应:

<?php
namespace plugin\my_recharge_plugin\controller;

class User
{
    /**
     * 挂载点渲染:返回视图(系统截取内容嵌入到挂载页面)
     */
    public function mount()
    {
        $data = []; // 准备区块所需数据(如余额、卡密记录等)
        return app('view')->fetch('user/mount', $data);
    }

    /**
     * 独立页面(可选):/user/plugin/插件标识 访问,挂载区块内可链接过来
     */
    public function index()
    {
        // ...
    }
}

插件视图放在 view/user/ 目录(如 view/user/mount.html),视图自带面板结构(标题、表单、列表等),系统渲染页面时会把该区块按顺序插入挂载页面。页面模板中的挂载区由系统自动注入 pluginMounts 变量,模板内无需单独声明(官方模板已内置挂载区)。

注意:挂载方法返回的必须是视图响应(\think\response\View);插件视图遵循模板规范:CSS/JS 放插件 static/ 目录对应文件,视图文件内禁止内联 <style>/<script>,文件使用无 BOM 的 UTF-8 编码。

插件主类(两种风格,推荐实例风格)

系统 PluginManager::hostAction() 同时支持两种插件风格,插件二选一实现:

风格一:静态 nodeAction(节点型插件)——先由系统解析产品关联的节点,再调用插件静态方法:

<?php
namespace plugin\my_node;

class My_node
{
    public static function nodeAction(array $node, array $product, array $host, string $action, array $extra = []): array
    {
        // 实现插件逻辑
    }

    public static function testConnection(array $node): array
    {
        // 测试节点连接
    }

    public static function panelApi(array $node, string $module, string $action, array $params = []): array
    {
        // 控制面板数据(模块 + 动作分发)
    }

    public static function getPhpVersions(array $node): array
    {
        // 可选:动态返回受控端已安装的 PHP 版本(节点型虚拟主机插件用)
    }
}

风格二:实例 hostAction(对接通用风格,推荐)——插件实例化时传入合并后的配置(合并顺序:插件全局 config → 上游分组 config(产品关联上游分组且分组有配置时)→ 产品 config,后者覆盖前者,产品配置优先级最高),方法签名更简洁,无需自行解析节点:

<?php
namespace plugin\demo_host;

class Demo_host
{
    /**
     * 构造注入:合并后的插件配置
     */
    protected array $config = [];

    public function __construct(array $config = [])
    {
        $this->config = $config;
    }

    /**
     * 服务操作(系统统一入口)
     * @param string $action create/renew/terminate/suspend/unsuspend/resetpwd/upgrade/login/test
     * @param array  $host   服务同构记录(含 config,系统已把主表/扩展表/host 专属列组装为统一数组;开通成功后 plugin_result 回写服务 config,底层按类型落主表 service_main.config 或 host.config)
     * @param array  $extra  额外参数(用户选择的配置等)
     * @note 服务类型判据:$host['type'] 由系统统一补齐(取值 host/vps/dedi/cdn,与产品类型一致),
     *       插件以此区分当前服务类型;hostAction 不传产品记录,勿依赖 $product
     */
    public function hostAction(string $action, array $host, array $extra = []): array
    {
        // 实现插件逻辑
    }
}
插件适配系统原则(核心):插件必须适配系统、遵循 PluginManager 契约,系统核心不因插件改动。例如对接第三方财务系统时,以「上游分组 + 对接插件」方式接入,而不是让系统为某个第三方单独改版。

操作类型说明

操作类型说明
create创建服务(同步:先开通后扣款,失败不扣款;异步:pending 扣款、failed 自动退款,详见「失败契约」与「扣款与退款流程」)
renew续期服务
terminate删除服务
suspend暂停服务
unsuspend启用服务
resetpwd重置密码
upgrade弹性升级(产品配置了弹性配置项即可发起,与产品类型无关,host/vps/dedi/cdn 均会派发;是否派发取决于产品的配置通道,见下节「动作派发矩阵」)
login一键登录(可选)
test测试连接(服务器分组级功能,见「测试节点调用契约」)

九动作入参契约($extra 实际内容,V1.3.3.5.8 实测):系统调用 hostAction($action, $host, $extra) 时,各动作的 $extra 如下——插件按此读取参数,勿假设系统会传其他键:

动作$extra 实际内容
create['domain' => 绑定域名, 'period' => 周期标识(month/quarter/...)](另含地区映射 region_id 等产品配置内键)
renew['period' => 周期标识, 'setdate' => 新到期日期(Y-m-d)]
upgrade['new_config' => 新弹性配置(option_key => value)]
terminate(用户端到期删除)['refund_type' => '2', 'reason' => '客户申请到期删除(不退费)'](refund_type=2 表示到期删除、不立即退款)
suspend / unsuspend / login[] 空数组
resetpwd['password' => 系统生成的随机新密码(明文)]
test节点测试:分组配置整体注入插件,无额外键(见「测试节点调用契约」)

refund_type 完整枚举(terminate 入参):1=立即退款(上游立即退订并退款)、2=到期删除(不立即退款,到期后由上游回收)。当前系统用户端「到期删除」固定传 2;系统框架不主动传 1,立即退款由上游退订流程内部处理。

动作派发矩阵(产品配置通道 × 操作)

插件动作是否被派发,取决于产品如何配置(而非产品类型)。「产品配置通道」与各操作的派发关系如下(V1.3.3.5.8 实测):

产品配置通道createrenewupgradesuspend / unsuspend / resetpwd / loginterminate(到期删除)
自营直挂插件(product.plugin_id > 0)✓ 插件✓ 插件✓ 插件✓ 插件✓ 插件
自营分组(apply_mode=self,分组插件自动写入 plugin_id)✓ 插件✓ 插件✓ 插件✓ 插件✓ 插件
代理插件分组(apply_mode=agent,分组 plugin_name)✓ 插件✓ 插件✓ 插件✓ 插件✓ 插件
上游 API 供应商(api_provider,无插件)✓ 上游✓ 上游✗ 仅本地合并配置✓ 上游✓ 上游
纯自营(无插件无上游)✓ 本地✓ 本地✓ 本地(有弹性项)✓ 本地本地回收

说明:管理端服务操作统一走 upstreamHostAction(内含分组插件覆盖);内测版 V1.3.3.5.8 起,用户端 renew / upgrade / 动作通道同样识别代理插件分组(正式版 V1.2 及此前仅本地处理,正式版将在后续版本追上)(按 server_group.plugin_name 查插件派发;product.plugin_id 直挂仍优先),与上表一致。

读懂矩阵的两个前提(勿与字段表混淆):① 矩阵表头第一列「产品配置通道」中的 apply_mode=self / apply_mode=agent 是通道名(区分「自营分组」与「代理插件分组」两种配置方式),不是插件 plugin.json 字段表里 apply_mode 的取值(字段值为 1=仅自营 / 2=仅代理分销 / 3=通用)——两组「apply_mode」含义不同,勿混用;② 表中「✓ 插件 / ✓ 上游 / ✓ 本地」表示该配置通道下该操作由谁执行,不是插件是否支持该操作。

test(测试节点)的派发归属:「测试节点」不是按产品配置通道派发的动作,而是服务器分组级别的功能——管理端在「服务器分组 → 测试节点」发起时,调用该分组绑定的代理插件(config_mode=group 且分组填了 plugin_name)实例方法 hostAction('test')(官方 yuxin_upstream 走专用服务);自营直挂插件(product.plugin_id)不参与「测试节点」。见「测试节点调用契约」。

服务操作失败契约(terminate / upgrade 等)

服务操作失败契约(自 V1.3.3.5 起,按动作×通道实况):
动作同步失败(code=1)系统行为扣款 / 退款
create(同步返回失败)服务置开通失败(status=6)、订单保持待支付(pay_status=0)可删除;不落开通中不扣款(先开通、后扣费;失败无扣款可退,订单 remark 注明「服务开通失败,未扣款」)
create(异步:返回 pending → 轮询 failed)服务置开通失败(status=6),msg 写入 config.fail_reason 并记录失败时间已扣款(pending 视为已开通扣款)→ 系统自动退款(原路退回余额,幂等不重复退)
create(异步超 24h 仍 pending)服务置开通异常(status=7)转人工排查,fail_reason 写「开通超时(超过24小时)」已扣款但不自动退款(转人工处理)
renew调用失败直接终止,到期时间 / 服务状态保持不变不扣款(扣款在续费成功后执行)
upgrade调用失败直接终止,服务配置 / 到期时间保持不变不扣款(差价在升级成功后收取,失败无差价可退)
terminate / suspend / unsuspend / resetpwd / login系统不改动服务状态,把插件 msg 原样返回给用户提示不涉及扣款;terminate 失败服务保持原状态与到期时间,插件需自行完成上游侧回滚或补偿

通用原则:任一动作失败系统不会自动重试;除「异步 create failed」外均不涉及退款(系统扣款一律在动作成功之后,异步 pending 除外)。插件在 hostAction() 中自行完成上游侧的回滚或补偿(如删除失败时释放已创建的云端资源)。

重复调用与返回码处理(幂等约定):系统入口对动作有状态校验(非运行中服务不能发起删除 / 到期删除,仅开通失败服务可删除),同一服务不会被系统重复触发;但插件侧仍须保证动作幂等——同一资源重复调用(网络重试、用户连点)时,若上游动作已完成,应返回 code=0(如 terminate 第二次、资源已删应视为成功),而非返回 code=1 报错。返回码统一按下表处理:

返回码含义系统处理
code=0成功(含重复调用时动作已完成的幂等成功)按成功落库 / 扣款,继续后续流程
code=1业务失败(msg 带原因)按失败契约处理,不自动重试
异步(非 code=2)data.pending 标记,实例尚未就绪置开通中(status=5),由 queryStatus 轮询收尾

注意:系统没有 code=2。业务返回码只有 0 / 1;异步开通一律用 data.pending 标记,不要返回 code=2(系统不会按处理中识别)。

状态查询 queryStatus

插件可选择性实现 queryStatus($host, $product) 返回服务状态,用于异步开通轮询(create 返回 code:2 时系统周期轮询)与产品列表状态展示。返回示例:

return [
    'code' => 0,
    'msg'  => '成功',
    'data' => [
        'status' => 'active',   // active=开通成功 / failed=开通失败(系统自动退款)/ pending=处理中
        'instance_info' => ['ip' => '1.2.3.4', 'username' => 'root'], // 开通成功后由系统合并写入 config.upstream
    ],
];

服务类型判据:$host['type'] 由系统统一补齐(host/vps/dedi/cdn,与产品类型一致);queryStatus 中同时可参考 $product['type'](两者一致),插件按类型区分上游接口时以此为准。

异步开通收尾链路(必读):create 返回 pending(code=2)后,系统周期轮询(定时任务,约扫描开通中 status=5 的服务)调用 queryStatus,按返回收尾:

queryStatus 返回系统动作金额处理
active置开通成功(status=1),data.instance_info 合并写入 config.upstream已扣款(pending 时已扣)
failed置开通失败(status=6),msg 写 config.fail_reason + 失败时间自动退款(幂等,不重复退)
pending(持续)保持开通中;超过 24 小时置开通异常(status=7)转人工排查已扣款但不自动退款(转人工)

插件未实现 queryStatus 时,系统返回 pending(跳过状态查询)——异步开通会一直停在开通中,24 小时后落 status=7 转人工;因此对接异步上游的插件必须实现 queryStatus,否则开通结果永远无法自动收尾。

异步开通回写:轮询返回 active 时,data.instance_info 由系统自动合并写入服务 config.upstream(落点与 plugin_result 相同,按类型落 service_main.config 或 host.config),插件无需自行回写。

开通回写 plugin_result

插件 create 成功时,可在返回的 data 中回传 plugin_result(如受控端返回的虚拟机 ID、IP、SSH 账号等),系统会自动将其写入服务 config.plugin_result(覆盖语义、非合并;vps/dedi/cdn 落主表 service_main.config,虚拟主机落 host.config;插件在同构数组里仍通过 host.config.plugin_result 读取)。后续控制面板等操作通过该 ID 定位上游资源(云主机控制面板依赖此字段,缺失会提示"云主机ID缺失")。

键名契约(必读):config.plugin_result 中的键名无系统白名单校验——插件回传什么键系统就原样保存什么键(整键赋值,不做类型校验)。系统唯一强依赖的键是 id:云主机控制面板通过 config.plugin_result.id 定位上游虚拟机,请返回字符串类型的上游资源 ID(如 'vm_123456'),缺失或非字符串会导致控制面板报「云主机ID缺失」。其余键(ip / username / SSH 账号等)用于面板展示与排查,建议与 id 一并回传;id 在服务生命周期内应保持稳定,重装 / 迁移导致上游 ID 变化时需同步回写(见「刷新语义」)。不要依赖 plugin_result 累积跨次数据——create / renew / upgrade 每次刷新均为覆盖语义(整键覆盖),历史数据不会保留。

刷新语义:plugin_result 在 create(含异步开通 active 回写 instance_info)时写入;内测版 V1.3.3.5.8 起 renew / upgrade 成功后同样刷新(正式版 V1.2 及此前不刷新,正式版将在后续版本追上)——插件返回的 data.upstream / data.instance_info 浅合并进服务 config 顶层,data.plugin_result 整键覆盖(vps/dedi/cdn 落主表 service_main.config,虚拟主机落 host.config),无相关键不写库。重装系统为 KVM 受控端动作(yx_vm_reinstall),若上游虚拟机 ID 变化,需由插件/受控端在重装完成后将新 ID 同步回服务 config,系统不自动处理。

分键边界与读取兜底(必读):三个回写键的落点不同——data.plugin_result 整键覆盖 config.plugin_result;data.upstream / data.instance_info 是浅合并(array_merge)进 config 顶层,不进 plugin_result。插件读取时必须对键不存在做兜底,避免 Undefined index:

// 插件侧读取:键可能不存在,一律用 ?? 兜底
$vmId = (string) ($cfg['plugin_result']['id'] ?? '');
$upId = (int)    ($cfg['upstream']['id'] ?? 0);
if ($vmId === '') {
    // 上游 ID 缺失:走兜底/报错提示,不要继续假设资源存在
}

可选能力调用约定

以下能力均为插件可选方法(系统 method_exists 探测,未实现则界面提示「暂不支持」),调用时机与产品类型无关:

方法入参调用时机
listOptions上游产品ID(upstream_product_id)后台产品编辑页「预获取 / 一键同步弹性选项」
listPeriods上游产品ID(upstream_product_id)后台产品编辑页「同步周期价」
listRegions无入参后台产品编辑页「获取地区分类」
listImages上游产品ID(购买页取 config.upstream_product_id)后台产品编辑页「系统镜像」下拉数据源;购买页动态加载操作系统镜像(两级:系统类型 → 版本)

测试节点调用契约

上游分组「测试节点」实际调用:官方 yuxin_upstream 走专用服务 UpstreamYuxinIdcService::testConnection();其他代理插件走实例方法 hostAction('test')(分组配置整体注入插件)。静态风格的 testConnection(array $node) 在「测试节点」中不会被调用;插件未实现 hostAction 时后台提示「插件 [...] 不支持节点测试」。对接插件请使用实例风格并实现 hostAction 的 test 分支(用分组配置发起一次真实上游请求,返回 ['code'=>0,...] 即连通)。

返回值格式

return [
    'code' => 0,        // 0成功,1失败
    'msg' => '成功',     // 提示信息
    'data' => []         // 返回数据
];
插件配置页面:插件无需提供 config.html——系统在后台「插件管理」列表显示「配置」按钮,进入的配置页由系统按 plugin.json 的 config_params 统一渲染(支持 text/password/number/textarea/select/switch/radio 七类控件),并注入插件已保存的配置值;保存后写入插件记录的 config 字段,插件主类构造时接收该配置数组。插件自定义的 config.html 不会被系统读取。

最小可运行插件骨架

下面是一个把上述契约串起来的最小插件包(目录 plugins/plugins/demo_hello/),安装后即有:后台配置页 + 后台管理页 + 用户中心挂载块 + 一张插件表,可直接复制改:

plugin.json

{
    "name": "demo_hello",
    "title": "示例插件",
    "version": "1.0.0",
    "author": "你的署名",
    "type": "tool",
    "description": "最小示例",
    "config_params": [
        {"key": "welcome", "name": "欢迎语", "type": "text", "default": "你好"}
    ],
    "user_mount": {
        "home": {"title": "示例插件", "action": "mount"}
    }
}

Demo_hello.php(主类)

<?php
namespace plugin\demo_hello;

class Demo_hello
{
    protected array $config = [];

    public function __construct(array $config = [])
    {
        $this->config = $config;
    }

    public function hostAction(string $action, array $host, array $extra = []): array
    {
        return ['code' => 0, 'msg' => 'ok', 'data' => []];
    }
}

controller/User.php(用户中心挂载)

<?php
namespace plugin\demo_hello\controller;

use think\facade\View;

class User
{
    public function __construct()
    {
        // 把视图根切到插件自己的 view/ 目录,下面 fetch 路径相对它写
        View::config(['view_path' => dirname(__DIR__) . '/view/']);
    }

    public function mount()
    {
        $uid = \think\facade\Session::get('user')['id'] ?? 0;
        // 路径相对插件 view/ 根,所以要带 user/ 子目录
        return app('view')->fetch('user/mount', ['uid' => $uid]);
    }
}

controller/Admin.php(后台管理页)

<?php
namespace plugin\demo_hello\controller;

use think\facade\View;

class Admin
{
    public function __construct()
    {
        // 后台控制器同样要把视图根切到插件 view/ 目录
        View::config(['view_path' => dirname(__DIR__) . '/view/']);
    }

    public function index()
    {
        $rows = \think\facade\Db::name('demo_hello_log')->select();
        // 显式写模板名:相对插件 view/ 根,对应 view/admin/index.html
        return view('admin/index', ['rows' => $rows]);
    }
}

install.sql(建插件自带表,前缀写死 yxidc_)

CREATE TABLE IF NOT EXISTS `yxidc_demo_hello_log` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL DEFAULT '0',
  `remark` varchar(255) NOT NULL DEFAULT '',
  `create_time` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

视图文件(两个最小模板)

view/user/mount.html(用户中心挂载块,对应 User::mount() 传的变量):

<div class="plugin-block">
    <p>你好,用户ID:{$uid}</p>
</div>

view/admin/index.html(后台管理页,对应 Admin::index(),View 自动套后台布局):

<table class="table">
{volist name="rows" id="r"}
    <tr><td>{$r.id}</td><td>{$r.remark}</td></tr>
{/volist}
</table>
以上文件均为无 BOM 的 UTF-8;后台入口固定 /admin/plugin/manage?name=demo_hello&action=index,不要自己往系统 route 注册。视图都放在插件的 view/ 目录下:用户中心页按控制器约定放 view/user/,后台页放 view/admin/;而自定义面板模板按 panel.template 声明的名字直接放 view/{模板名}.html(在 view 根目录,不带 user/admin 子目录)。
四、 四、插件可配置参数

参数类型

类型说明前台显示
select下拉选择下拉框
radio单选单选按钮
text文本输入输入框
password密码输入掩码输入框
number数字输入数字输入框
textarea多行文本文本域
switch开关开关按钮

参数配置说明

{
    "key": "php_version",           // 参数键名
    "name": "PHP版本",              // 参数名称
    "type": "select",               // 参数类型
    "options": [                    // 选项(select/radio类型需要)
        {"value": "", "label": "纯静态"},
        {"value": "82", "label": "PHP 8.2"}
    ],
    "default": "82",                // 默认值
    "user_selectable": true,        // 是否允许用户在购买时选择
    "dynamic": true,                // 是否动态获取选项(如PHP版本从受控端获取)
    "description": "站点运行的PHP版本"  // 参数描述
}

用户可选择参数

当 user_selectable 设置为 true 时,用户在购买产品时可以在购买详情页选择该参数的值。用户选择的值会覆盖产品的默认配置,并在开通服务时传递给插件。

动态参数

当 dynamic 设置为 true 时,参数选项由插件在运行时从受控端 / 上游动态获取(例如 PHP 版本列表读取受控端实际已安装的版本),而不是在 plugin.json 中写死。

虚拟主机类插件可配置参数示例

参数键名参数名称类型用户可选择动态默认值
php_versionPHP版本select是是82
db_version数据库版本select是否57
db_charset数据库字符集select否否utf8mb4
create_db创建数据库radio是否是
create_ftp创建FTPradio是否是
space_size空间大小number否否1024
db_size数据库大小number否否100
五、 五、模板开发指南

模板目录结构(V1.1.3.2 起)

模板安装在系统根目录 view/template/开发者标识/模板名/ 下,一个模板包包含以下内容:

view/template/开发者标识/模板名/
├── template.json          # 模板清单(字段说明见下文 template.json 字段说明)
├── view/                  # 模板视图文件(PHP引擎读取,浏览器无法直接访问)
│   ├── home/              # 前台模板
│   ├── user/              # 用户中心模板
│   ├── admin/             # 后台模板(仅官方默认模板)
│   └── agent/             # 代理端模板(仅官方默认模板)
└── static/                # 静态资源(可选,随模板包一起打包)
    ├── css/
    ├── js/
    └── images/
静态资源发布机制:模板包中的 static/ 目录必须随模板一起打包发布。安装/更新模板时,系统会自动把 static/ 发布到 public/static/template/模板名/ 目录供浏览器访问。view/ 目录是 PHP 引擎读取的模板源码,浏览器无法直接访问,静态资源不能只放在 view/ 下,否则页面加载不到样式。

静态资源引用规范

系统已启用 ThinkPHP 模板字符串替换(tpl_replace_string,自 V1.1.3.7 起)。模板中引用静态资源应使用 __STATIC__ 前缀,编译时自动替换为 /static,不要写死 /static/ 路径(便于子目录部署与路径调整):

<link rel="stylesheet" href="__STATIC__/template/modern/theme.css">
<script src="__STATIC__/template/modern/app.js"></script>
<img src="__STATIC__/template/modern/images/logo.png" alt="">

模板自己的静态资源位于 /static/template/模板名/(安装模板时由系统自动从模板包 static/ 发布),系统公用 JS/CSS(__STATIC__/css/、__STATIC__/js/common.js、__STATIC__/js/dialog.js 等)可直接调用,模板开发时应优先复用公用文件,避免重复编写、确保联动统一。

兼容性:使用 __STATIC__ 写法要求 IDC 系统版本 ≥ V1.1.3.7(该版本起系统启用 tpl_replace_string)。在更早版本系统上部署模板时,请改回写死 /static/ 路径。

template.json 字段说明

模板根目录下的 template.json 是模板清单文件,系统安装/更新模板时自动读取。字段说明如下:

字段类型必填说明
name字符串是模板标识(英文/数字/下划线),用于目录名和模板唯一识别,如 modern、ling
title字符串是模板显示名称,后台和前台市场展示用,如「现代响应式模板」
version字符串是模板版本号,格式 主.次.修订,如 1.3.51,每次更新必须递增
developer字符串是开发者标识(仅支持字母、数字、下划线和连字符),对应 view/template/开发者标识/ 目录名,同一开发者所有模板请填同一个 developer;系统安装时会对该字段做字母数字清洗,中文等非字母数字字符会被过滤(如 jsy、芸 会变成 jsy),请勿使用中文署名;yuxin 为官方保留标识,第三方请勿使用
author字符串是开发者署名(中文显示名),前台市场展示用,官方作者为「jsy、芸」
description字符串否模板描述,前台市场卡片展示,建议 50 字以内
min_version字符串否最低兼容系统版本,如 1.3.3(不带 V 前缀)。授权站按客户端上报的系统版本做更新提醒兼容闸:系统版本 ≥ 该值才推送此新版,不足则回落旧版或提示不可更新;留空 = 不限制(所有版本均可收到更新提醒)。注意:客户端安装/更新时不会校验此字段,兼容过滤只发生在授权站市场接口

示例:

{
    "name": "my_template",
    "title": "我的模板",
    "version": "1.3.51",
    "developer": "your_id",
    "author": "你的署名",
    "description": "模板简短描述,前台市场卡片展示用"
}

模板文件清单

模板包 view/ 目录下建议提供以下文件,系统按路由自动匹配;缺失的文件会静默回退官方模板对应页面,不报错:

前台(home/)

文件路径对应路由建议提供说明
home/layout.html—是前台公共布局(头部/导航/底部),所有页面继承此布局
home/index/index.html/是首页
home/product/index.html/product是产品列表页(含类型tab、地区分组)
home/product/detail.html/product/:type/:id(兼容 /product/detail/:id)是产品详情页(通用,按产品类型自动匹配子模板)
home/article/index.html/news(列表)是文章/公告列表页
home/article/detail.html/news/:id(详情)是文章/公告详情页
home/auth/login.html/login是用户登录页
home/auth/register.html/register是用户注册页
home/auth/findpwd.html/findpwd是找回密码页
home/install/index.html/install否安装向导页(仅全新安装时需要)

用户中心(user/)

文件路径对应路由建议提供说明
user/layout.html—是用户中心公共布局(含侧边栏)
user/sidebar.html—是用户中心侧边栏菜单(被layout引入)
user/user/index.html/user是用户中心首页/概览
user/user/profile.html/user/profile是个人资料编辑
user/user/password.html/user/password是修改密码
user/order/index.html/user/order是我的订单列表
user/service/index.html/user/service是我的服务列表
user/service/panel.html/user/service/panel是服务管理面板入口
user/finance/index.html/user/finance是财务流水
user/recharge/index.html/user/recharge是充值页
user/ticket/index.html/user/ticket是工单列表
user/message/index.html/user/message是站内信列表
user/invoice/index.html/user/invoice否发票管理
user/apikey/index.html/user/apikey否API 密钥管理(app_key/app_secret 创建与查看)
user/notice/index.html/user/notice否系统通知(未提供时前台公告列表 home/article/index.html 承担)

产品详情页子模板

产品详情页(/product/detail)是核心转化页面,系统按产品 type 字段自动匹配对应子模板:

产品类型 type匹配文件说明
host(虚拟主机)home/product/detail_host.html虚拟主机购买页
vps(云服务器/VPS)home/product/detail_vps.htmlVPS购买页(弹性规格、镜像选择)
dedi(独立服务器)home/product/detail_dedi.html独立服务器购买页
cdn(CDN)home/product/detail_cdn.htmlCDN购买页
子模板回退规则:对应类型的子模板不存在时,自动回退到通用 home/product/detail.html。开发者若只做一套通用详情页,只需提供 detail.html 即可;若需针对不同产品类型做差异化购买页,再分别提供对应子模板。
缺文件行为:系统按 场景/控制器/方法.html 路径匹配视图(场景即 home 或 user,对应模板包内 view/ 下的目录),找不到对应文件时自动回退到官方默认模板(yuxin/default),不会报错。开发者只需重点定制前台首页、产品列表、产品详情和用户中心布局,其余页面可先复用官方模板样式。

模板切换

系统支持在后台分别切换前台模板与用户中心模板。模板安装后位于 view/template/开发者标识/ 目录,管理员在「后台 → 模板管理」中选择启用即可。

模板变量

V2.6 起,前台(home 场景)公共注入 $controller / $action / $config(要求系统版本 ≥ V1.3.3)。用户中心场景由 UserBase 注入用户相关变量。下表为「场景 × 变量」矩阵,未标注的变量在该场景下不存在,模板必须判空处理。

变量前台 home用户中心 user说明
controller✓(V1.3.3+)✓(V1.3.3+)当前控制器名(小写),如 product、article、user、service,导航高亮可直接使用
action✓(V1.3.3+)✓(V1.3.3+)当前操作名(小写),如 index、detail、panel
config✓(V1.3.3+)✓(V1.3.3+)系统配置数组(安全白名单过滤后的键,见下方键名清单)
siteName✓✓站点名称,如 {$siteName|default='语信IDC财务管理系统'}
isLogin✓✓是否已登录(1/0)
userInfo✗✓当前登录用户信息数组(id/username/money/balance/is_agent),未登录场景不存在,需判空
activeMenu✗✓用户中心侧边栏高亮标识,取值 index/order/service/finance/recharge/invoice/ticket/apikey/message/notice/profile/password,系统按控制器/操作自动赋值
pluginMounts✗✓(挂载点页)插件挂载区块列表,数组元素含 content(HTML字符串),仅在挂载点对应页面非空
isPortal✗✓虚拟主机门户会话标识(1/0),门户登录态下隐藏用户中心导航
unreadMessageCount✗✓未读站内信数量
安全写法(重要):所有变量都可能不存在(如未登录、旧版本、非挂载点页面),引用前必须判空,否则会触发 Undefined variable 报错(站点开启 app_debug 时直接白屏)。推荐写法:
  • 变量输出:{$siteName|default='语信IDC财务管理系统'}、{$userInfo.username|default=''}
  • 条件判断:{if condition="isset($controller) and $controller eq 'product'"} 或 {if condition="!empty($controller) and $controller eq 'product'"}
  • 区块判断:{notempty name="pluginMounts"}…{/notempty}
导航高亮建议优先使用 {if condition="!empty($controller) and $controller eq 'product'"}is-active{/if},避免依赖未注入变量。

键名清单(安全过滤后的常用键,其余键名以实际为准):

键名说明
site_name站点名称
site_notice站点公告
site_url站点网址
register_open是否开放注册(1/0)
portal_enabled / portal_domain虚拟主机独立门户开关/域名
invoice_enabled / invoice_types / invoice_notice发票开关/类型/说明
expire_remind_enabled / expire_remind_days到期提醒开关/提前天数
mail_enabled / mail_from_name邮件开关/发件人
安全说明:$config 仅注入非敏感配置,密钥类键(app_secret、smtp_pass、授权码等)一律不注入模板,模板无需也无法读取,请勿依赖。
版本兼容说明(重要):「场景 × 变量」矩阵中的注入范围与系统版本有关:
  • $controller / $action / $config 仅 V1.3.3+(含正式版 V1.3.3 及全部内测版)注入;V1.2 及更早版本不存在这三个变量
  • 兼容写法(推荐,任何版本均可用):导航高亮用 JS 按 URL 比对(参考官方 modern 模板做法),模板变量一律判空({$siteName|default=''} / {notempty} / !empty()),不依赖注入变量
  • 若模板强依赖 $controller,请在 template.json 声明 min_version: 1.3.3;注意 min_version 的兼容过滤只发生在授权站市场更新提醒,客户端安装/更新时不会校验该字段(详见「模板/插件发布与更新机制」章节)
  • 官方 modern 模板兼容策略:|default='语信IDC财务管理系统' 兜底 + JS 按 URL 高亮,V1.2 与 V1.3.3 表现一致,可作参考模板
n

时间格式化标准写法

模板中输出日期/时间,统一使用系统模板引擎内置的 date 函数修饰符,格式如下:

{$vo.create_time|date='Y-m-d'}
{$vo.create_time|date='Y-m-d H:i'}
{$vo.create_time|date='Y-m-d H:i:s'}
智能兼容:引擎自动判断字段值——时间戳数值直接格式化,日期字符串自动转时间戳(等价于 is_numeric($val) ? $val : strtotime($val)),无需在模板中手动判断。
禁止写法(重要):不要使用 {$vo.create_time|date="Y-m-d",###} 这类带 ### 占位符的写法。模板引擎内置的 date 修饰符不会替换 ###,编译产物会残留 ### 导致 PHP 语法错误(ParseError: unexpected token ";"),页面直接 500 白屏。参数一律用单引号,不带 ,###。

路由口径(官方推荐写法)

以下为系统官方路由,模板链接请按此口径编写,避免 404:

页面官方路由说明
产品列表/product支持 ?type=host|vps|dedi|cdn 类型筛选、?region_group_id=&region_id= 地区筛选
产品分组/product/group/:id按分组查看产品
产品详情/product/:type/:idtype 取值 xunizhuji(虚拟主机)/yunfuwuqi(云服务器)/cdn/dulifuwuqi(独立服务器),兼容 /product/detail/:id;不推荐 /product/host/3 这类写法
文章列表/news统一用 /news,/article 非官方路由(默认路由偶然可达,不建议依赖)
文章详情/news/:id不存在时 302 回列表;/article/1 会 404
登录 / 注册 / 找回密码/login /register /findpwd—

路由拼音 ↔ 产品 type 别名映射(必读):URL 路由用拼音(对用户友好),产品 type 字段、子模板命名、插件字段用英文,两者是固定别名,系统自动双向转换:

路由拼音(URL)产品 type(英文)含义子模板
xunizhujihost虚拟主机detail_host.html
yunfuwuqivps云服务器detail_vps.html
cdncdnCDNdetail_cdn.html
dulifuwuqidedi独立服务器detail_dedi.html

站内接口契约(用户中心提交操作)

以下接口为对外稳定契约,模板/插件均可调用(版本间保持兼容):

操作接口参数
登录POST /doLoginusername、password
注册POST /doRegisterusername、email、password、repassword、ref、invite_code
下单POST /user/order/buyproduct_id(产品ID)、period_id(product_period 记录ID,非 month/quarter 字符串)、domain(type=host 必填)、host_password(可空,留空用预生成)、user_config(JSON 字符串)
预生成主机名GET /product/purchaseHost?id=返回 data.host / data.password;插件未实现时返回 code=1「产品不存在」
续费GET /user/service/renew?host_id= + POST /user/service/doRenewdoRenew 参数:id、period_id
升级GET /user/service/upgrade?host_id= + POST /user/service/doUpgradedoUpgrade 参数以升级页表单为准(配置档位)
服务操作(暂停/恢复/重置密码/登录)POST /user/service/actionid(服务ID)、act(login/resetpwd/suspend/unsuspend)
面板操作POST /user/service/panelApihost_id、module、action、params(JSON 字符串)
删除服务POST /user/service/deleteid
下单响应约定(重要):下单返回 code=0 但 data.plugin_success=false 表示「订单已创建、服务开通失败」,模板必须展示 data.plugin_msg 给用户,不要直接当成功处理。
周期半年付:产品周期表 product_period 支持 half_year 档位,站内下单用 period_id 可完整支持半年付;开放 API /api/order/create 的 duration 仅接受 month/quarter/year,站内与 API 口径不同。

公共 JS API

系统公共 JS(__STATIC__/js/common.js、__STATIC__/js/dialog.js)提供以下稳定公开 API,模板应优先复用:

API说明
window.Yx.request(url, {method,data,success,error,toast,raw})通用请求(GET/POST),自动处理登录态与错误提示
window.Yx.get(url, opts) / window.Yx.post(url, data, opts)快捷 GET/POST 请求
window.YxDialog.alert(msg) / confirm(msg, cb)美化弹窗确认
window.YxDialog.toast(msg, 'success'|'error'|'warning')顶部滑出提示,2.5秒自动消失(替代原生 alert)
window.alert / window.confirm已被系统重写为美化弹窗,模板可直接调用,无需额外处理

模板继承与包含

{literal}{extend name="layout" /}
{block name="title"}页面标题{/block}
{block name="content"}
    页面内容
{/block}{/literal}
包含路径规范(重要):本节规则仅适用于系统模板(视图根目录已包含场景目录 home/user/admin)。{include file="xxx" /} 的路径是相对视图根的,不要写场景前缀。例如用户中心的侧边栏文件在 view/user/sidebar.html,应写 {include file="sidebar" /},不能写 {include file="user/sidebar" /}(会被解析成 view/user/user/sidebar.html 导致「模板文件不存在」报错)。注意与插件侧不同:插件里 app('view')->fetch('user/mount') 的路径是相对插件自身 view/ 根目录的,必须带 user/、admin/ 子目录,两者视图根不同不要混。
局部文件命名规范:允许使用下划线前缀 _panel_xxx.html、_sidebar.html 作为局部包含文件(系统按「场景/控制器/方法.html」匹配路由页面,下划线前缀文件不会被当页面路由,安全)。推荐统一 _ 前缀,便于识别局部文件。
条件标签引号规范:{if} 标签条件内使用单引号,如 {if condition="$controller eq 'index'"},不要使用 \" 转义引号(模板引擎不剥离转义,会编译成 PHP 语法错误)。
HTML 注释勘误(重要):模板引擎不跳过 HTML 注释,注释里的 {include ...} / {extend ...} 会被当作真实标签编译。不要在 <!-- ... --> 注释内书写任何模板标签,否则会触发自包含或编译错误。

模板自检清单(上传前必查)

  • 文件编码:全部 UTF-8 无 BOM
  • 视图文件内无内联 <style> / <script>(静态资源放 static/ 并用 __STATIC__/template/模板名/ 引用)
  • 时间日期输出统一用 {$var|date='Y-m-d'} 写法(单引号、不带 ,###)
  • 所有模板变量引用均判空(|default='' / {notempty} / !empty()),不依赖未注入变量
  • {if} 条件内用单引号,无 \" 转义;HTML 注释内无模板标签
  • 文件按需提供(非强制必需):缺失文件静默回退官方模板;建议至少提供 home/layout.html、home/index/index.html、home/product/index.html、home/product/detail.html、home/article/index.html、home/article/detail.html、home/auth/{login,register,findpwd}.html、user/layout.html、user/sidebar.html、user/{user,order,service,finance,recharge,ticket,message}/index.html
  • template.json 完整(name/title/version/developer/author,可选 min_version/description)
  • {include file="xxx" /} 不带场景前缀;挂载点只在对应页面输出一次
六、 六、控制面板开发(虚拟主机 / 云主机)

概述

系统支持自研控制面板:虚拟主机(站点/数据库/FTP等)与云主机(VPS 电源/重装/重置密码等)均可在系统内直接管理,无需跳转第三方面板。控制面板采用插件化架构,插件通过 panel 声明 + 面板分发方法实现。

控制面板架构

控制面板采用插件化架构,虚拟主机(站点 / 数据库 / FTP 等)与云主机(VPS 电源 / 重装 / 重置密码等)使用同一套面板分发机制:插件在 plugin.json 中用 panel 声明前端标签(enabled / title / template / tabs),面板数据请求统一由 PluginManager::panel() 分发到插件的 panelApi($node, $module, $action, $params)(静态方法或实例方法均可,内测版 V1.3.3.5.8 起支持实例风格,正式版 V1.2 及此前仅静态风格)。系统只负责渲染面板框架、校验服务归属并转发请求,具体每个模块怎么实现由插件自行决定。

面板分发契约(panelApi)

前端面板 AJAX 请求 POST /user/service/panelApi(或 /user/host/panelApi),携带 host_id / module / action / params,系统校验服务归属后注入 params.host_id,调用插件方法(静态风格 public static function panelApi(...) 或实例风格 public function panelApi(...),内测版 V1.3.3.5.8 起系统按 ReflectionMethod::isStatic() 判定分发(正式版 V1.2 及此前仅静态风格);实例风格下构造函数注入合并后的 config,与 hostAction 同口径):

public static function panelApi(array $node, string $module, string $action, array $params = []): array
{
    // $node 为上游节点配置(无节点的插件可忽略)
    // $module 面板功能域(如 overview / domain / php / rewrite / ssl / ftp / backup)
    // $action 该功能域下的具体动作(如 list / add / del / current / set)
    // $params 前端传入参数,已含 host_id,可再取 id、值等
    switch ($module) {
        case 'overview':
            if ($action === 'info') {
                // 返回概览信息
            }
            break;
        case 'domain':
            if ($action === 'list') {
                // 域名列表
            }
            break;
        // ... 其他模块由插件自行定义
    }
    return ['code' => 0, 'msg' => '成功', 'data' => []];
}
module / action 由插件自定义:系统不规定固定的模块名与动作名,插件按自身面板需要划分 module(功能域)并在其下定义 action(具体操作);panel.tabs 只声明前端标签页,与 module 不强制一一对应。未实现 panelApi 的插件,系统返回友好提示而不报错。

云主机(KVM)控制面板

自研 KVM 受控端对接插件(type: vps)通过 plugin.json 的 panel 声明控制面板,系统自动渲染面板框架并分发数据请求:

"panel": {
    "enabled": true,
    "title": "云主机控制面板",
    "template": "panel_kvm",
    "tabs": [
        {"key": "overview", "name": "主机概览"},
        {"key": "console", "name": "控制台"},
        {"key": "power", "name": "电源管理"},
        {"key": "os", "name": "重装系统"},
        {"key": "password", "name": "重置密码"},
        {"key": "monitor", "name": "性能监控"},
        {"key": "network", "name": "网络管理"}
    ]
}

面板数据接口统一为 POST /user/service/panelApi(参数:host_id、module、action、params=JSON 字符串;host_id 为对外沿用的服务标识,系统内部先按主表 service_main、未命中再按 legacy_host_id 映射解析),系统按 panel.template 加载对应视图模板(如 panel_kvm.html),前端切换 tab 时调用页内 switchTab(key) 重新请求对应模块数据。云主机资源ID存于服务 config.plugin_result.id(开通时受控端回传的虚拟机 ID,底层落主表 config,插件拿到的同构数组仍以 host 键呈现),缺失时会提示"云主机ID缺失"。

七、 七、上游分组与第三方系统对接

概述

系统区分两种货源方式:自营(使用自己的受控端 / 面板,或自研 KVM 受控端等)与代理 / 对接(对接第三方财务系统,或同系统互联)。对接第三方统一走「上游分组」机制,系统核心不因第三方单独改版——由对接插件适配系统(PluginManager 契约)。

「自营受控端」的界定(重要):此处「受控端 / 面板」专指官方自研 kvm-agent(KVM 受控端)及官方随系统提供的受控端 / 面板,不含第三方虚拟化平台(PVE / Proxmox、ESXi、Hyper-V、OpenStack、Xen 等)。对接第三方虚拟化平台属于代理 / 对接货源方式:插件 config_mode=group、经上游分组挂载,apply_mode 取 2(仅代理分销)或 3(通用),不能按自营(apply_mode=1)发布。本文档《自研 KVM 受控端对接》所涉受控端均指官方 kvm-agent。

核心流程

  1. 安装对接插件:在后台「插件管理」安装并启用对接第三方系统的插件(如第三方对接插件),在插件配置页填写 API 站点、API Key 等基础信息;或在「上游分组」中新建分组时选择对接插件,直接在该分组填写 API 配置(一个对接插件可对接多个站点,各分组独立配置)
  2. 添加上游分组:后台「产品系统 → 上游分组」新建分组,选择对接插件后填写该站点的 API 地址 / Key / Secret 等配置项(按插件声明的 config 显示,不再显示通用 JSON 高级配置)
  3. 测试节点:在分组操作中点「测试节点」,验证与上游 API 连通性(返回成功后才可用于产品关联;实际调用方法见「插件开发指南 · 测试节点调用契约」)
  4. 添加产品:后台「产品管理 → 添加产品」,选择所属分组后选择货源方式(自营 / 代理),代理时下拉选择上游分组,并可通过「一键获取」拉取上游地区的产品列表、系统镜像、弹性配置等,选择上游产品后自动联动填入产品配置
  5. 购买联动:前台购买页按产品类型渲染(服务器 GB / 虚拟主机 MB),系统镜像两级联动(操作系统类型 → 版本),购买时系统调用上游接口开通,并把上游返回的资源 ID 写入服务 config.plugin_result(vps/dedi/cdn 落主表 service_main.config,虚拟主机落 host.config)

上游分组配置

字段说明
分组名称上游站点标识,如"上游-上海节点"
对接插件选择已安装的代理/对接插件(下拉仅列出 config_mode=group 的插件,即对接类插件;自研/全局配置类插件不出现,不按产品类型过滤)
适用产品类型多选该分组可服务的产品类型(host / vps / dedi / cdn 等),可多选;不勾选或勾选 raw = 通用(可服务所有类型)。产品页「上游分组」下拉按此过滤——一个分组绑定一个对接插件即可服务多类型产品
API 配置按插件声明的配置项显示(API 地址 / Key / Secret 等),配置保存在该分组内,多个站点可各填一套
状态 / 排序启用状态与显示顺序
测试节点通过后,该分组即可在添加产品时被选择;配置合并顺序为 插件全局 config → 上游分组 config → 产品 config,后者覆盖前者(PluginManager::hostAction 依次合并),产品配置优先级最高。

对接插件选项契约(listOptions)

对接插件通过 listOptions 返回上游产品的弹性配置选项,系统据此在后台「添加产品」与前台「购买详情页」渲染输入控件。插件必须声明每个选项的输入方式 control,系统按契约渲染,不感知上游命名(过滤哪些选项、如何取最低档等也由插件自行处理)。

// listOptions 返回结构
{
    "code": 0,
    "msg": "成功",
    "data": {
        "host": { "hostname": "ecs", "password": "xxx", "port": 22 },  // 开通参数(主机名/密码/远程端口)
        "min_config": { "cpu": 1, "memory": 2, "disk": 20, "ip_count": 1 }, // 最低配置(自动填入产品默认配置)
        "skip_options": { "region_option_id": 223, "os_option_id": 224 },   // 需跳过的选项ID(如系统镜像由购买页联动获取)
        "groups": [                     // 弹性配置分组
            {
                "option_key": "ip_count", // 选项键名(唯一,对应 yxidc_product_option.option_key)
                "name": "IP数量",          // 显示名称
                "unit": "个",              // 单位
                "control": "slider",       // 输入方式(见下表)
                "qty_min": 1,              // 数量型最小值(slider/stepper/number)
                "qty_max": 4,              // 数量型最大值
                "unit_price": 11,          // 单价(元/单位,数量型按 数量 × 单价 计价)
                "options": [               // 选项列表(select/radio/fixed 使用)
                    { "id": 622, "label": "1个", "price": 11 },
                    { "id": 197629, "label": "2个", "price": 16 }
                ]
            }
        ]
    }
}
control 值输入方式前台渲染依赖字段
select下拉选择下拉框(档位,每个档位对应价格)options
radio单选卡片单选按钮组options
number数字输入数字输入框(自动校验范围)qty_min / qty_max / unit_price
slider滑块调整进度条滑块 + −/+ 按钮 + 当前值(可单点加减、可拖到最大值)qty_min / qty_max / unit_price
stepper加减器−/+ 步进器qty_min / qty_max / unit_price
fixed固定展示固定值(不可选)options
存量兼容:插件未声明 control 时,系统按 input_type 默认降级(qty → slider、fixed → fixed、select → select),旧产品无需重配。后台「添加产品」编辑器也可手动调整每个选项的输入方式、最小值 / 最大值 / 单价;数量型选项按 数量 × unit_price 实时参与计价。

对接插件可选能力契约一览

以下方法均为可选实现,系统调用前先以 method_exists 判断;插件未实现某能力时,对应环节退回后台手动填写 / 跳过,不影响已实现部分。所有方法统一返回 {code:0|1, msg, data},code=0 为成功,code=1 为失败并在 msg 说明原因。

方法用途未实现时系统行为
listProducts()拉取上游产品分类树,后台一键选择上游产品后台手动填写上游产品ID
listPeriods($productId)同步上游周期价格到产品周期定价表后台手动维护周期与价格
listImages($productId)拉取系统镜像(系统类型 → 版本两级联动)后台手动填写系统镜像
queryStatus($host, $product)轮询实例开通状态,驱动开通成功 / 失败落库不轮询,由后台人工核对
listRegions()拉取上游地区 / 区域列表后台手动维护地区
purchaseHost($productId)预生成上游主机名 / 初始密码(可选)开通时由上游直接分配

产品分类树契约(listProducts)

无参实例方法,供后台代理模式产品编辑页「获取上游产品」调用,返回两级分类 + 产品的树,前端级联渲染「一级分类 → 二级分类 → 上游产品」,选中后把上游产品ID回填产品的 upstream_product_id。上游没有多级分类时可裁剪层级(如产品直接挂在分类下,省略 products 层)。

// listProducts() 返回结构
{
    "code": 0,
    "msg": "获取上游产品成功",
    "data": {
        "categories": [
            {
                "id": "10",
                "name": "云服务器",
                "children": [
                    {
                        "id": "101",
                        "name": "经济型",
                        "products": [
                            { "id": "8801", "name": "1C1G 20M 套餐" },
                            { "id": "8802", "name": "2C2G 30M 套餐" }
                        ]
                    }
                ]
            }
        ]
    }
}
字段说明
categories[].id一级分类ID(字符串 / 整数均可)
categories[].name一级分类显示名
categories[].children[]二级分类数组
children[].id / name二级分类ID / 显示名
children[].products[]该分类下的上游产品列表(无二级时可直接放在 children)
products[].id上游产品ID,选中后写入 upstream_product_id
products[].name上游产品显示名

周期价格契约(listPeriods)

实例方法 listPeriods(int $productId = 0),供后台「同步周期价」调用,把上游各周期价格写入产品周期定价表(product_period)。$productId 传 0 时由插件从自身配置的 upstream_product_id 读取。系统按 period 键存在即更新(upsert),写入价格、开通费与显示名。

// listPeriods($productId) 返回结构
{
    "code": 0,
    "msg": "周期价格同步成功",
    "data": {
        "periods": [
            { "period": "month",      "name": "月付",   "price": "30.00",  "setup_fee": "0" },
            { "period": "quarter",    "name": "季付",   "price": "85.00",  "setup_fee": "0" },
            { "period": "half_year",  "name": "半年付", "price": "160.00", "setup_fee": "0" },
            { "period": "year",      "name": "年付",   "price": "300.00", "setup_fee": "0" }
        ]
    }
}
period 键含义说明
month月付标准周期
quarter季付标准周期
half_year半年付可选周期,上游不支持则不返回
year年付标准周期
与对外开放 API 的区别:此处 period 是对接插件与系统内部产品周期定价表之间的键,可包含 half_year;对外开放下单 API /api/order/create 的 duration 仅接受 month / quarter / year,两者口径不同,不要混用。

系统镜像契约(listImages)

实例方法 listImages(int $productId = 0),供后台产品镜像配置与前台购买页「操作系统类型 → 版本」两级联动调用。返回扁平数组,系统按 group 自动聚合成第一级「系统类型」,每条 name 作为第二级「版本」,id 为提交购买时回传给上游的镜像标识。

// listImages($productId) 返回结构(扁平数组,前端按 group 聚合)
{
    "code": 0,
    "msg": "获取系统镜像成功",
    "data": [
        { "id": "centos79", "name": "CentOS 7.9",  "group": "CentOS", "pricing": 0 },
        { "id": "ub2204",   "name": "Ubuntu 22.04", "group": "Ubuntu", "pricing": 0 },
        { "id": "win2019",  "name": "Windows 2019", "group": "Windows", "pricing": 20 }
    ]
}
字段说明
id镜像标识,购买时回传上游
name版本显示名(第二级下拉项)
group系统类型(第一级下拉分组名,缺失归入「其他」)
pricing可选,该镜像附加价格(元),不支持则省略或为 0

开通状态轮询契约(queryStatus)

实例方法 queryStatus(array $host, array $product),系统定时任务轮询开通中的实例,依据返回 status 驱动主机状态落库。插件查询上游实例状态后归一化为三种结果,由系统统一处理,插件不直接改库。

服务类型判据:$host['type'] 由系统统一补齐(host/vps/dedi/cdn,与产品类型一致);queryStatus 中亦可参考 $product['type'](两者一致),插件按类型区分上游接口时以此为准。

// queryStatus($host, $product) 返回结构
{
    "code": 0,
    "status": "active",            // active | failed | pending
    "instance_info": {             // active 时返回,合并进服务 config.upstream(同构数组视图 host.config.upstream)
        "upstream": true,
        "ip": "1.2.3.4",
        "remote_info": {}
    },
    "msg": "实例已开通"
}
status含义系统处理
active开通成功服务置为正常(status=1),把 instance_info 合并写入服务 config.upstream(vps/dedi/cdn 落主表 service_main.config,虚拟主机落 host.config;插件同构数组仍以 host.config.upstream 呈现)
failed开通失败主机置为开通失败(status=6),msg 写入 config.fail_reason 并记录失败时间;已扣款项由系统自动退款(原路退回用户余额,写入 finance_log type=refund 与 service_log action=refund,订单标记退款状态与退款备注,幂等不重复退)
pending开通中 / 查询中保持原状态继续等待;超过 24 小时仍 pending 由系统标记为开通异常(status=7)转人工排查
扣款与退款流程(区分同步 / 异步,自 V1.3.3.5 起;行为以 V1.3.3.5.8 为准):
  • 同步通道(create 直接返回结果):系统先开通、后扣款——开通成功才扣款并置订单已支付(pay_status=1);开通失败(code=1)不扣款,订单保持待支付(pay_status=0,可删除)、服务置开通失败(status=6)、remark 注明「服务开通失败,未扣款」。
  • 异步通道(create 返回 pending):上游订单已支付、实例异步开通中,系统置服务开通中(status=5)并按订单金额扣款;此后周期轮询 queryStatus——返回 active 则置开通成功(status=1);返回 failed 则置开通失败(status=6)并自动退款(把订单实付金额原路退回用户余额,写入 finance_log type=refund 与 service_log action=refund,订单标记退款状态与退款备注,幂等不重复退);超过 24 小时仍 pending 则置开通异常(status=7)转人工排查,不自动退款。

版本线提醒:上述行为以内测版 V1.3.3.5.8 为实测基准;正式版当前为 V1.2(尚未包含本流程),正式版发布至 V1.3.3.5.x 后适用。插件无需也不得自行处理退款;异步上游可安全接入,不会再出现"钱已收、资源没开"无人处理的情况。

入参:$host 为当前主机记录(含 config.plugin_result 等开通回传),$product 为产品记录;插件据此定位上游实例,不自行写库。

地区与预生成主机名(listRegions / purchaseHost,可选)

方法返回结构用途
listRegions()data.regions[{id, name}](可附 option_id)后台地区分类页「获取上游地区」,返回可选机房 / 区域
purchaseHost($productId)data.host, data.password开通前向上游预取主机名与初始密码,上游不需要时省略
十一、 十一、用户等级/会员体系

系统内置用户等级/会员体系,支持不同等级用户享受不同折扣,自动升级,提升用户留存和付费意愿。

功能特点

  • ✅ 自定义等级:管理员可自定义等级名称、升级条件、折扣率
  • ✅ 自动升级:用户消费或下单达到条件后自动升级
  • ✅ 自动折扣:下单时自动应用对应等级折扣
  • ✅ 进度显示:用户中心显示当前等级、升级进度

默认等级

等级升级条件折扣
普通用户默认无折扣
白银会员累计消费满1000元95折
黄金会员累计消费满5000元9折
钻石会员累计消费满20000元85折

后台配置

管理员可在「用户等级管理」中:添加/编辑/删除等级、设置升级条件(累计消费金额或累计下单数量)、设置折扣率、设置等级排序和状态。

用户中心展示

  • 显示当前等级图标和名称
  • 显示累计消费金额
  • 显示距离下一级还需消费多少
  • 显示升级进度条
十二、 十二、数据库表结构

核心表

表前缀说明:系统支持自定义表前缀,安装时可填写,默认 yxidc_。以下表名中的 {prefix} 代表实际配置的表前缀,系统代码中不写死表前缀,全部使用数据库配置联动。
表名说明
{prefix}product产品表
{prefix}product_group产品分组表
{prefix}product_period产品周期定价表
{prefix}server_group上游分组表(对接第三方站点,选插件+API配置)
{prefix}service_main通用服务台账主表(host/vps/dedi/cdn 全类型生命周期统一维护,系统资源定位根)
{prefix}service_vps云服务器(vps)1:1扩展表(主表 service_main 的类型专属属性)
{prefix}service_dedi独立服务器(dedi)1:1扩展表
{prefix}service_cdnCDN 1:1扩展表(预留)
{prefix}host虚拟主机专属表(仅承载 type=host 的域名/账号等虚拟主机属性;不再承载 vps/dedi/cdn)
{prefix}service_log服务操作日志表(续费/升级/启停/删除等操作流水)
{prefix}order订单表
{prefix}order_item订单明细表(service_id 关联主表,host_id 为虚拟主机旧映射,读侧 service_id 优先、host_id 回退)
{prefix}user用户表(含level_id字段,关联用户等级;余额字段为 money)
{prefix}user_level用户等级表(会员体系配置)
{prefix}finance_log资金流水表(用户余额每笔变动,type/amount正负/balance变动后余额/relate_id关联订单)
{prefix}commission_log佣金流水表(代理/推广返佣记录)
{prefix}withdraw提现申请表(agent_id/amount/account/status 0待审1通过2驳回3打款)
{prefix}plugin插件表
{prefix}template模板表
{prefix}update_history系统更新历史表
注意:各对接插件自己的数据表(如节点表、短信验证码表等)由插件自带,安装对应插件时通过插件的 install.sql 创建,不属于系统核心表,表前缀同样跟随系统配置。

产品表结构

字段类型说明
idint产品ID
group_idint分组ID
plugin_idint关联插件ID(自营 / 对接插件)
server_group_idint上游分组ID(代理货源时关联,0=自营)
upstream_provider_idint上游供应商ID(对接第三方时)
upstream_product_idstring上游产品ID(对接第三方时,一键获取上游产品后自动填入)
namestring产品名称
typestring产品类型:host(虚拟主机) / vps(云服务器) / dedi(独立服务器) / cdn(CDN)
descriptiontext产品描述
configjson产品配置(弹性规格、默认镜像等;代理时可一键同步上游最低配置)
statusint状态:1启用,0停用
is_recommendint是否推荐
sortint排序
create_timeint创建时间
update_timeint更新时间

通用服务台账主表 service_main 结构(资源定位根)

系统全部服务类型(host 虚拟主机 / vps 云服务器 / dedi 独立服务器 / cdn)的生命周期统一落在主表 service_main;类型专属的定位/规格等属性存放在 1:1 扩展表(service_vps / service_dedi / service_cdn),其余未列化的开通参数与上游回写仍存 config JSON。虚拟主机的 domain/username/password 等专属属性仍在 host 表。

字段类型说明
idint主表服务ID(系统内部资源定位主键)
user_idint所属用户ID
agent_idint归属代理ID
product_idint产品ID
server_group_idint上游分组ID(代理货源服务)
typestring服务类型:host / vps / dedi / cdn
legacy_host_idint旧 host.id 映射(仅 host 虚拟主机与双写期历史记录使用;vps/dedi/cdn 新单为 0)
configjson开通配置快照 + 未列化专属属性;开通成功后系统回写 plugin_result(含上游资源ID/IP等)
statusint服务状态:1正常,2已暂停,3已到期,4已删除,5开通中,6开通失败,7开通异常(超时待人工排查)
remarkstring客户自用备注(仅本人可见)
expire_timeint到期时间
create_timeint创建时间
update_timeint更新时间
插件契约不变:系统内部会把主表 + 扩展表 + host 专属列重新组装成同构数组再传给插件的 hostAction / queryStatus / panelApi,插件仍按原 $host 数组与 config.plugin_result 取资源ID,无需插件作者感知底层表拆分。

虚拟主机专属表 host(仅 type=host)

字段类型说明
idint虚拟主机记录ID(即主表 legacy_host_id)
domainstring绑定域名
usernamestring虚拟主机用户名
passwordstring虚拟主机密码
server_group_idint上游分组ID(代理货源服务)
configjson虚拟主机配置 + plugin_result 回写
statusint服务状态:1正常,2已暂停,3已到期,4已删除,5开通中,6开通失败,7开通异常
expire_timeint到期时间
create_time / update_timeint创建/更新时间

类型专属扩展表(1:1,service_id 关联主表)

扩展表承载类型主要列化字段
{prefix}service_vpsvps 云服务器service_id、vm_id(上游实例ID)、ip、username、password、ipv4(全部IPv4)、image_os(镜像/系统)
{prefix}service_dedidedi 独立服务器service_id、sn(序列号)、ip、bandwidth(带宽)、room(机房)
{prefix}service_cdncdn(预留)service_id、cdn_domain(加速域名)、origin(回源地址)、traffic(流量/额度)
扩展表仅列化常用、需独立检索的属性;其余开通参数与上游回写统一存主表 config JSON。系统对外仍组装为同构服务数组,插件不直接读写扩展表。插件需要持久化的数据只有三个合法落点:① 服务 config 顶层键(开通 / 续费 / 升级时通过 data.upstream / data.instance_info 浅合并回写,或 data.plugin_result 整键覆盖);② config.plugin_result 内(上游实例信息,面板与排查用);③ 插件自建表(install.sql 建表,卸载时由 uninstall.sql 清理)。系统不提供插件直写扩展表(service_vps / service_dedi / service_cdn)的接口,也不保证直写结果会被读取。

产品分组表

字段类型说明
idint分组ID
namestring分组名称
typestring分组类型:host(虚拟主机) / vps(云服务器) / dedi(独立服务器) / cdn(CDN)
sortint排序
statusint状态
说明:「对接插件」下拉只按分组的配置模式过滤(config_mode=group 的插件可选),与分组类型(host/vps/dedi/cdn)和 apply_mode 均无关——分组类型仅影响产品归属展示,不决定插件可选范围;apply_mode 在保存时按分组开通模式互斥校验(冲突自动清空插件)。
十三、 十三、插件上架与销售

概述

授权站提供开发者中心,开发者可以发布插件、模板、服务,通过销售获得收益。

开发者功能

  • 插件/模板发布:进入 GET /developer/productAdd,在页面内选择类型(插件/模板)与分类,提交保存到 POST /developer/productSave;编辑走 /developer/productEdit/:type/:id,更新走 POST /developer/productUpdate
  • 经营方式:发布时选择「自营」或「代理对接」,避免自营与对接产品混淆(自营=使用自己的受控端,此处受控端专指官方自研 kvm-agent,不含第三方虚拟化平台;对接=对接第三方系统/同系统API,第三方虚拟化平台(PVE / ESXi / Hyper-V / OpenStack 等)属对接类,发布时请选「代理对接」)
  • 源码上传:发布插件/模板时需要上传源码压缩包
  • 版本管理:支持多版本发布。发布新版本 = 更新该商品的最新版本包,只会让已购买并安装该商品的用户收到更新提醒;全新商品需用户先购买安装后才进入更新检测。版本号必须递增,包内清单 name 必须与商品标识完全一致,min_version 兼容闸规则见「模板/插件发布与更新机制」章节
  • 服务市场:开发者可发布服务(如运维、部署、定制开发),支持接单与交付流程(/developer/service)
  • 收益管理:查看销售收益、余额、提现
  • 手续费设置:后台可配置销售手续费和提现手续费

手续费机制

后台 /admin/developer/fee 可配置(两项费率均允许 0 ~ 50%):

  • 销售手续费(developer_sale_fee):用户购买插件/模板时从售价中扣除的比例,默认 10%,剩余计入开发者余额
  • 提现手续费(developer_withdraw_fee):开发者申请提现时从提现金额中扣除的比例,默认 2%,余额先按销售费率入账,提现时再扣提现费

计算示例

(销售手续费20%,提现手续费3%):

  • 插件售价:100元
  • 扣除销售手续费20%:到余额80元
  • 申请提现扣除3%:实际到手77.6元

发布页实际到手计算

开发者发布插件/模板时,价格输入框下方会显示实际到手金额模拟计算:

  • 输入售价
  • 自动计算扣除销售手续费后的余额
  • 自动计算扣除提现手续费后的实际到手
  • 显示计算公式说明

市场展示

授权站前台市场页展示所有已审核通过的插件(/market/plugin)、模板(/market/template);服务(运维 / 部署 / 定制开发)由开发者中心发布、接单与交付,在前台服务市场页展示:

  • 显示开发者用户名与身份徽章(开发者/专业版/企业版等)
  • 显示发布日期
  • 点击开发者可进入开发者主页,查看该开发者发布的所有插件/模板
  • 市场按系统分类展示(IDC 财务系统 / KVM 受控端),每页最多 8 个,超出自动分页

购买与下载

  • 授权站不提供在线下载窗口,防止资源被复制盗用;用户购买插件/模板后,在自购的 IDC 财务系统后台「系统商店」中校验授权并下载安装/更新
  • 安装包 / 更新包 / 插件 / 模板下载均带授权校验(需有效授权码),防止直连下载链接被盗链
  • 授权站本地已安装系统更新通道不受影响(演示站等已授权站点正常校验更新)

管理员审核

授权站后台 /admin/developer/product 审核开发者发布的插件/模板:

  • 显示插件/模板类型(插件/模板)
  • 显示开发者上传的源码压缩包,可下载审核
  • 审核通过后前台展示
  • 可编辑、删除、上下架
十四、 十四、模板/插件发布与更新机制

核心机制:更新提醒只推给「已安装」的用户

用户侧(IDC 财务系统后台)检测插件/模板更新时,是拿本地已安装的商品标识(name)去授权站市场匹配的:本地装过哪个商品,才检测哪个商品的更新。因此:

  • 发布新版本 = 更新该商品的最新版本包,只会让已经购买并安装该商品的用户收到更新提醒;
  • 全新商品(从未被任何人安装过):用户需要先到市场购买、到系统后台「系统商店」安装,安装后才进入更新检测;
  • 不存在「发布新版本 = 全站用户都能看到提醒」的广播机制。
常见误解:「我发布了新版本,为什么我的客户没有收到更新提醒?」——请先确认:① 该客户是否已安装你的商品;② 若客户系统版本低于你填的 min_version,也会被兼容闸回落(见下文)。

红线:包内清单的 name 必须与市场商品标识完全一致

安装包根目录下的 template.json(模板)/ plugin.json(插件)中,name 字段必须与授权站市场商品的「标识」完全一致(即发布产品时填写的商品标识,如 your_theme、your_plugin)。

如果不一致会怎样?系统按包内 name 安装到对应目录,安装后本地记录的商品标识是包内 name,而不是市场商品标识。之后用户侧检测更新时按包内 name 去市场匹配,永远匹配不到你发布的这个商品,导致「永远提示可更新却永远更新不完」或「安装后成为另一个商品、与市场商品脱节」。更换 name 等同于发布一个全新商品,老用户(装在旧 name 目录下)将收不到后续更新——商品标识发布后请勿更改。

安装目录规则:模板由 developer 决定,插件由 name 决定

模板安装目录 = view/template/开发者标识/模板名/,开发者标识取 template.json 里的 developer 字段(字母+数字/下划线/连字符,author 是署名、不参与目录)。插件安装目录 = plugins/plugins/插件标识/,由 plugin.json 的 name 字段决定,插件清单不需要 developer 字段(插件更新匹配同样按 name)。

同一开发者请固定填同一个 developer(模板):你开发的所有模板都填同一个 developer 标识,系统会把它们全部安装到同一个开发者目录下(如 your_id/modern、your_id/my_template2 并列)。若每套换一个 developer,模板会被分散安装到多个目录,后台列表与目录管理都会变得混乱。

注意:developer 必须是字母+数字(a-zA-Z0-9,可含下划线/连字符,安装时会自动清洗非法字符),不要把中文署名填进 developer 字段——中文会被清洗成残缺目录名(如「开发者署名、工作室」这类中文,清洗后只剩残缺字符),导致模板装进错误目录。

安装包 zip 内部目录结构

系统安装插件/模板包时,会自动在 zip 内定位含 plugin.json(插件)/ template.json(模板)的目录作为源码根目录,因此以下三种打包形态均可正确安装:

打包形态zip 内部结构是否支持
a)根目录直放zip 根下直接是 plugin.json 与主类文件✅ 推荐
b)一层目录包裹zip 根下有一层以插件标识命名的目录(标识/plugin.json)✅ 兼容
c)完整相对路径zip 内含完整路径(plugins/plugins/标识/plugin.json)✅ 兼容

推荐使用形态 a(zip 根目录直接放置清单与源码),最规范简洁;形态 b / c 虽兼容,但中间的包裹目录不会还原到安装目录,系统仅安装定位到的插件内容。

额外文件:允许在插件源码目录内附带 README.md、开发/运维脚本、截图等文件,它们会随插件一起安装;放在源码目录之外(zip 根层或包裹层)的文件会被丢弃,不影响安装。

安装定位:系统按清单内 name 字段安装到 plugins/plugins/{name}/(插件)或 view/template/{developer}/{name}/(模板),并非按包内路径原样还原;模板包与插件包为同一套定位规则,已有同名模板就地覆盖更新。

min_version 兼容闸(重要)

授权站按客户端上报的系统版本对每个版本包做兼容过滤(系统后台检测更新时会带上自己的版本号;正式版与内测版版本线隔开校验:正式版商品的 min_version 按客户端正式版版本号匹配,内测版商品按内测版版本号匹配,未上报或版本不足对有门槛的新版保守回落旧版):

min_version 填写效果
留空(不填)不限制,所有系统版本均可收到该版本的更新提醒(存量老商品默认)
填写如 1.3.3客户端系统版本 >= 1.3.3 才会收到该新版;版本不足的客户端回落到低门槛的旧版本包(或提示不可更新)
特别注意:如果你的新版兼容旧版系统(例如正式版 V1.2 用户也要能用),min_version 请留空或填 ≤ 旧版最低版本,不要填成高于旧版的值——否则 V1.2 正式版用户收不到你的新版本提醒(这正是「正式版没收到模板更新提醒」的最常见原因)。

说明:min_version 只影响授权站市场的更新提醒过滤;客户端安装/更新时不会校验该字段。

版本号规则

  • 版本号格式 主.次.修订(如 1.0.0 → 1.0.1),每次发布必须递增,不能重复或回退;
  • 系统侧按 version_compare 比较版本号判断是否有更新,版本号写错会导致「检测不到更新」或「反复提示更新」。

发布新版本操作流程

  1. 进入开发者中心 → 我的插件 / 我的模板 → 找到商品点「版本」;
  2. 点击「发布新版本」:填写递增的版本号、更新说明(用户侧会展示)、上传该版本安装包(zip);
  3. 如需限制最低系统版本,填写 min_version(不确定请留空)。min_version 的作用是限制授权站客户端「检测到更新」的版本门槛:客户端系统版本低于该值时,不提示该版本更新(客户端上报 system_version 匹配);它不会在安装 / 更新时做强制校验,更不是「不满足就不能安装」的硬闸。正式版与内测版按版本线分别校验:正式版只认正式版号、内测版只认内测版号(例如 min_version 填 V1.3.3.5.8 只对同线版本生效)。想全兼容请留空;
  4. 提交后版本进入版本列表,已安装该商品的用户系统即可检测到更新。
打包自检:发布前务必确认安装包内清单 name 与商品标识一致(插件按 name 安装、模板 developer 填字母+数字标识、不要填中文署名)、无 BOM、目录结构符合开发文档要求,并本地实测安装/更新一遍再发布。自查:开通流程是否直连第三方虚拟化平台 API(PVE / ESXi / Hyper-V / OpenStack 等)?是——请按对接类发布(config_mode=group + apply_mode 取 2/3,经上游分组挂载),不要按自营(apply_mode=1)。
十五、 十五、常见问题

1. 如何开发一个新插件?

  1. 在 plugins/plugins/ 目录下创建插件目录
  2. 创建 plugin.json 配置文件
  3. 创建插件主类,实现 nodeAction(静态风格)或 hostAction(实例风格,推荐)方法
  4. 如需自建控制面板:在 plugin.json 声明 panel(enabled/title/template/tabs),并实现 panelApi($node, $module, $action, $params)(静态或实例方法均可,内测版 V1.3.3.5.8 起支持实例风格,正式版 V1.2 及此前仅静态风格)处理面板数据
  5. 在后台插件管理中安装并启用插件

2. 插件的可配置参数如何使用?

在 plugin.json 中定义 config_params,设置 user_selectable 为 true 的参数可以让用户在购买时选择。用户选择的值会保存在服务配置中,开通服务时传递给插件。

3. 如何对接第三方系统?

  1. 安装对应的对接插件(插件适配系统,遵循 PluginManager 契约,系统核心不因第三方改动)
  2. 后台「产品系统 → 上游分组」新建分组,选择对接插件并填写该站点的 API 地址 / Key / Secret
  3. 点击「测试节点」验证连通性
  4. 「产品管理 → 添加产品」选择所属分组、货源方式(代理)与上游分组,通过「一键获取」拉取上游产品/镜像/弹性配置并联动填入

4. 插件开通服务失败怎么办?

  1. 检查插件配置是否正确
  2. 检查上游分组节点连接是否正常(后台上游分组中测试节点)
  3. 查看错误日志,定位问题
  4. 资金口径:同步开通先开通后扣款、失败不扣款;异步开通 pending 已扣款,failed 自动退款,超 24h 不退款转人工(详见「扣款与退款流程」)
  5. 开通失败的订单和服务支持用户删除
  6. 联系插件开发者获取支持

5. 开发者如何发布插件/模板?

  1. 在授权站注册账号,申请成为开发者
  2. 进入开发者中心,点击「发布产品」
  3. 选择产品类型(插件/模板/服务),选择分类与经营方式(自营 / 代理对接)
  4. 填写产品信息,上传源码压缩包
  5. 提交审核,等待管理员审核通过
  6. 审核通过后前台展示,用户购买后在系统后台「系统商店」下载安装
  7. 已发布的商品发布新版本:开发者中心 → 我的插件/我的模板 → 找到商品 → 「版本」→「发布新版本」,填写递增版本号与更新说明并上传新包(详见「模板/插件发布与更新机制」章节)

6. 云主机控制面板如何扩展?

在插件 plugin.json 的 panel 中声明 enabled/title/template/tabs,系统自动渲染面板框架;面板数据统一走 POST /user/service/panelApi(host_id/module/action/params),插件实现对应模块操作即可。资源ID取服务 config.plugin_result.id(开通时受控端回传,同构数组里以 host 键呈现,底层按类型落主表 config 或 host.config)。

panel.template 取值规则(系统按以下顺序选模板):

template 声明系统行为
不填type=vps 的插件用系统内置 panel_kvm;其余类型 / 代理模式产品用系统内置 panel_universal 通用面板
"universal"强制使用系统内置通用面板 panel_universal(支持 VPS/SSH/网站等多种类型)
其他自定义名系统去插件目录 view/{该名}.html 找模板;存在则用插件自带面板模板,不存在则回退到上面的系统默认

panelApi 返回:统一返回 ['code'=>0,'msg'=>...,'data'=>...],data 由前端面板框架按 module 渲染成表格/表单/操作按钮;需要跳转或提示时用 msg + 前端 toast,不要在插件里直接输出 HTML 页面。

7. 如何让插件功能显示在用户中心?

在插件 plugin.json 中声明 user_mount 挂载点(如 {"recharge": {"title": "插件名", "action": "mount"}}),并提供 controller/User.php 实现对应方法返回视图。系统会把插件区块直接渲染到用户中心对应页面内(账户充值/财务明细/站内信/用户中心首页),区块标题直接显示插件名。用户中心侧边栏不会出现"插件服务"分组,插件无需额外菜单。

8. 插件高频报错怎么定位?

现象先查这里
提示"类不存在 / 插件类不存在"主类文件名必须 = 目录名首字母大写、下划线原样保留(如 my_demo → My_demo.php,不要写成驼峰 MyDemo.php),命名空间 \plugin\my_demo\My_demo;子目录 model/lib 类要自行 require_once,系统不自动加载
提示"该插件无后台/用户中心功能"对应控制器文件名必须是 controller/Admin.php 或 controller/User.php,命名空间分别为 \plugin\标识\controller\Admin / ...\User
建表失败 / 表不存在install.sql 里表名写死 yxidc_ 前缀(系统自动替换成实际前缀),并建议使用 CREATE TABLE IF NOT EXISTS(重装会再次执行 install.sql,须自行保证幂等);复杂初始化目前不支持 install.php,只能写在 install.sql
后台"管理"按钮点进去 404 / 报错统一走 /admin/plugin/manage?name=标识&action=方法名,方法名不能下划线开头;不要自己往系统 route 里注册插件路由
错误日志位置插件安装/卸载失败记在 runtime/plugin_install_error.log、runtime/plugin_uninstall_error.log;用户中心插件异常记 runtime/plugin_dbg.txt

日志格式说明:均为单行追加(时间 插件[名] 操作: 错误信息),系统不做自动轮转/清理,长期运行后可由运维按需清理 runtime 目录。受限权限部署下若 runtime 不可写,安装会失败但系统不中断流程——此时请检查 runtime 目录写权限。

9. 发布新版本后为什么用户收不到更新提醒?

最常见的原因有三种:

  1. 该用户还没有安装你的商品——更新检测只匹配本地已安装的商品标识(name),全新商品需用户先到市场购买安装;
  2. 包内清单 name 与市场商品标识不一致——系统按包内 name 安装并记录,之后按包内 name 去市场匹配,永远匹配不到你的商品(发布新版本前务必核对一致);
  3. min_version 高于用户系统版本——授权站按客户端上报的系统版本过滤更新提醒,版本不足会回落旧版或提示不可更新(想全版本兼容就留空或填 ≤ 目标旧版本)。
八、 八、自研 KVM 受控端对接

概述

自研受控端(kvm-agent,Go 实现)提供 KVM 虚拟机全生命周期管理 API,IDC 财务系统通过 kvm 插件对接,实现云服务器(VPS)自动化开通、续费、删除、启停、重置密码、重装系统、弹性升级及云主机控制面板。

鉴权签名

受控端接口使用 HMAC-SHA256 签名鉴权,请求头携带:

请求头说明
Yx-Key接入标识(与受控端 config.json 的 yx_key 一致)
Yx-Ts当前时间戳(秒)
Yx-Sign签名,算法见下
$body = json_encode($data);                 // 请求体
$signStr = $key . "\n" . $ts . "\n" . hash('sha256', $body);
$sign    = hash_hmac('sha256', $signStr, $secret);   // Yx-Sign

核心接口

接口说明
yx_node_info节点信息(状态/资源)
yx_os_list系统镜像列表(两级:系统类型 → 版本)
yx_vm_create创建虚拟机,返回 yx_vm_id / yx_ipv4 / yx_os_user / yx_passwd
yx_vm_info虚拟机详情
yx_vm_action电源操作(开机 / 关机 / 重启)
yx_vm_reinstall重装系统
yx_vm_resetpwd重置密码
yx_vm_upgrade弹性升级(CPU / 内存 / 带宽 / 系统盘)
yx_vm_delete删除虚拟机
插件配置:kvm 插件 config_params 提供受控端地址(yx_api_url)、接入标识(yx_key)、签名密钥(yx_secret)、默认系统镜像(os_name)、默认 SSH 账号(default_user),与受控端 config.json 保持一致。
九、 九、内置SSH终端

系统内置Web SSH终端功能,用户无需安装第三方SSH工具,直接在浏览器中即可连接和管理云服务器。

功能特点

  • ✅ 零配置:无需安装额外软件,浏览器直接使用
  • ✅ 安全:通过系统统一认证,密码不明文传输
  • ✅ 易用:支持命令复制粘贴、快捷键操作
  • ✅ 兼容:支持所有标准Linux命令

使用说明

  1. 用户在「我的服务」中找到已开通的云服务器
  2. 点击「管理面板」进入控制面板
  3. 切换到「SSH终端」标签页
  4. 点击「连接」按钮,即可开始命令行操作

技术说明:系统使用phpseclib3库实现SSH连接,采用HTTP长轮询方式,无需额外配置WebSocket服务,兼容所有PHP环境。

相关API接口

接口方法说明
/user/service/sshExecPOST执行SSH命令
🔌 API接口文档目录
API接口文档
API接口文档

IDC财务系统提供API接口,供第三方系统对接资源池,实现产品查询、订单创建、主机管理、账户查询等功能。

IDC财务系统API对接接口

IDC财务系统提供以下API接口,供第三方系统对接资源池,实现产品查询、订单创建、主机管理、账户查询等功能。

基础URL:http://your-domain.com/api 数据格式:JSON 字符编码:UTF-8 请求方式:GET / POST

认证方式

所有API接口都需要进行身份认证。用户需要在语信IDC财务系统的用户中心 → API密钥管理中创建API密钥,获取 app_key 和 app_secret。

注意:app_secret 只在创建时显示一次,请妥善保管。如果忘记了,请删除旧密钥并重新创建。
授权版本要求:开放 API 对接功能仅在专业版 / 企业版授权下可用,标准版请求会被中间件拒绝(403)。

三个认证参数既可放在请求头(X-App-Key / X-Timestamp / X-Sign),也可直接放在请求参数(app_key / timestamp / sign)中,中间件优先取请求头。

请求头参数:

参数名类型必填说明
X-App-Keystring是API密钥的app_key
X-Timestampint是当前时间戳(秒),与服务器时间差不能超过300秒
X-Signstring是请求签名,详见签名规则

签名规则

为了保证接口调用的安全性,所有请求都需要进行签名验证。签名算法如下:

  1. 将所有请求参数(除 sign 外)按参数名的字母顺序升序排列
  2. 将排序后的参数拼接成 key1=value1&key2=value2&... 的格式
  3. 在字符串末尾拼接 &app_secret=你的app_secret
  4. 对最终字符串进行 MD5 哈希,得到32位小写签名
  5. 将签名作为 sign 参数传入

PHP签名示例代码:

function makeSign($params, $appSecret) {
    ksort($params);
    $signStr = '';
    foreach ($params as $k => $v) {
        $signStr .= $k . '=' . $v . '&';
    }
    $signStr .= 'app_secret=' . $appSecret;
    return md5($signStr);
}

返回格式

所有接口返回JSON格式数据:

{
  "code": 0,
  "msg": "操作成功",
  "data": { }
}
字段类型说明
codeint状态码,0表示成功,非0表示失败
msgstring提示信息
datamixed返回数据,可能是对象、数组或空数组

响应码说明

接口响应分为业务响应码(控制器返回,所有接口统一)与鉴权层状态码(中间件在签名 / 密钥校验阶段返回)两层,不要混用。

1. 业务响应码(正常进入接口后返回):

code含义说明
0成功业务处理成功,数据在 data 中
1业务失败具体原因以 msg 为准,常见:产品不存在或已下架、订单不存在、主机不存在、余额不足、购买/续费周期不正确、域名格式错误等

2. 鉴权层状态码(未通过中间件校验时返回):

code触发条件处理建议
401缺少 app_key / timestamp / sign;时间戳格式错误;与服务器时间差超过 300 秒;API密钥不存在;签名验证失败检查三个认证参数是否齐全、时间戳是否为当前秒级、签名算法与参与签名的参数是否正确
403API密钥已被禁用;或当前授权版本不支持 API(需专业版 / 企业版)到用户中心启用密钥,或联系管理员升级授权版本
429调用频率超限(超过该密钥每小时配额)稍后重试,或联系管理员提升每小时配额

产品接口

产品列表 GET /api/product/list - 获取可购买的产品列表

参数名类型必填说明
pageint否页码,默认1
limitint否每页数量,默认10
typestring否产品类型:host(虚拟主机)、vps(云服务器)、dedi(独立服务器)、cdn(CDN)
group_idint否产品分组ID

产品详情 GET /api/product/detail - 获取单个产品的详细信息

参数名类型必填说明
idint是产品ID

产品分组 GET /api/product/groups - 获取产品分组列表

订单接口

订单列表 GET /api/order/list - 获取当前用户的订单列表

订单详情 GET /api/order/detail - 获取单个订单的详细信息

创建订单(购买产品) POST /api/order/create - 创建订单并购买产品,先开通后扣款——开通成功才扣款,开通失败不扣款。接口同步返回,内部同时支持同步 / 异步开通:插件返回 pending 时 host_status=5(开通中),异步 failed 自动退款、超 24h 不退款转人工。

参数名类型必填说明
product_idint是产品ID
durationstring是购买周期:month(月付)、quarter(季付)、year(年付)
domainstring条件必填绑定域名,虚拟主机类型产品必填,支持一级域名、二级域名、泛域名
quantityint否购买数量,默认1
主机状态说明:1 = 正常(已开通),2 = 已暂停,3 = 已到期,4 = 已删除,5 = 开通中,6 = 开通失败(同步开通失败不扣款;异步 failed 已扣款自动退款、超 24h 不退款转人工,订单标记退款状态),7 = 开通异常(超时待人工排查)

主机接口

主机列表 GET /api/host/list - 获取当前用户的主机列表

主机详情 GET /api/host/detail - 获取单个主机的详细信息,包括FTP、数据库等配置信息

主机操作(续费等) POST /api/host/action - 对主机进行操作,目前支持续费操作

参数名类型必填说明
idint是主机ID
actstring是操作类型:renew(续费)
durationstring续费必填续费周期:month(月付)、quarter(季付)、year(年付)

账户接口

用户信息 GET /api/account/info - 获取当前API密钥关联的用户信息(含等级信息)

账户余额 GET /api/account/balance - 获取当前用户的账户余额

财务明细 GET /api/account/finance - 获取当前用户的财务明细记录

充值记录 GET /api/account/recharge - 获取当前用户的充值记录

用户等级/会员体系

用户等级功能说明:系统内置用户等级/会员体系,支持不同等级享受不同折扣,自动升级。
接口方法说明
GET /api/account/infoGET返回用户当前等级、累计消费、升级进度等信息

返回字段说明:

字段说明
level_id用户等级ID
level_name等级名称(如:普通用户、白银会员、黄金会员、钻石会员)
level_discount等级折扣率(1.00=无折扣,0.95=95折)
total_consume累计消费金额
next_level下一个等级信息(如有)
upgrade_progress升级进度(0-100%)
系统在线更新机制

系统采用全新的在线更新机制,支持正式版 / 内测版两通道发布,安全可靠的分步更新流程。

更新通道

通道说明适用场景
正式版稳定版本,经过充分测试生产环境用户
内测版测试版本,包含新功能和修复内测用户、插件开发者

更新流程

发布节奏:正式版非紧急情况每周三打包更新,内容只包含已在内测版验证稳定的改动集合;内测版随改随发(新功能与修复先行发布)。插件/模板开发者建议使用内测版联调新契约,生产环境请使用正式版。

  1. 检查更新:系统自动检测是否有新版本
  2. 下载更新包:下载更新包到本地临时目录
  3. 文件校验:校验更新包文件完整性和一致性
  4. 备份文件:自动备份需要覆盖的文件
  5. 解压覆盖:解压更新包,覆盖系统文件
  6. 数据库升级:自动执行数据库升级脚本,兼容跨版本升级
  7. 更新完成:清除缓存,完成更新

跨版本升级支持:系统支持从任意旧版本直接升级到最新版本,自动执行中间所有版本的数据库升级脚本,无需逐版本更新。

数据库自动升级

每个版本的更新包都包含update.php数据库升级脚本,系统会自动:检测需要执行哪些数据库升级、创建新表/添加新字段/修改表结构、插入默认数据、升级失败自动回滚。

更新日志

V2.13(2026-11-02,V2.12 复检函逐条核对修订)

  • P0-1:修正 plugin_result 首段「合并写入」为覆盖语义(代码实测 create/renew/upgrade 均整键覆盖);补分键边界与读取兜底写法
  • P0-5:清理 FAQ、API 订单接口、API 主机状态三处「下单即扣款」残留,统一为先开通后扣款 / 失败不扣款口径
  • P0-7:五处「自 V1.3.3.5.8 起」补版本线限定(内测版起;正式版 V1.2 及此前为旧行为)
  • P1-8:补 install.sql 破坏性语句禁令(禁止 DROP TABLE / TRUNCATE,清理写 uninstall.sql)
  • P0-2:补 apply_mode 字段值 1/2/3 与派发矩阵映射,明确只决定能否选中、不决定派发
  • P0-3:补重复调用幂等约定与返回码处理(系统仅 code=0/1,异步用 data.pending,无 code=2)
  • 模板:补全安全写法空变量名($controller)、路由拼音↔英文别名映射、文件清单统一为建议提供(缺失静默回退)、清理重复提示
  • 其他:补 refund_type 枚举(1=立即退款 / 2=到期删除)、resetpwd 插件侧安全提示;内部用语「待官方裁定已实锤」改为「经实测确认」

V2.12(2026-11-02,开发者 12 条补丁核对修订)

  • 插件开发指南:plugin_result 补键名契约(无白名单校验;id 唯一强依赖、返回字符串、保持稳定;覆盖语义不累积)
  • 插件开发指南:动作派发矩阵补「读懂矩阵的两个前提」(表头 apply_mode=self/agent 是通道名非字段值)+ test 派发归属
  • 插件开发指南:失败契约按动作×通道整表重写——create 同步失败不扣款、异步 pending 扣款 failed 自动退款、超 24h status=7 不退款、renew/upgrade 失败不扣款、terminate 等失败不改状态
  • 插件开发指南:新增九动作入参契约表(create/renew/upgrade/terminate/suspend/unsuspend/login/resetpwd/test 的 $extra 实测内容)
  • 插件开发指南:pricing 强调实例方法(静态声明不生效);$scene 仅 new/renew,upgrade 不经过
  • 插件开发指南:install.sql 补「插件更新(覆盖安装)时同样执行」——表不重建,新增字段须 information_schema 判重补列
  • 插件开发指南:queryStatus 补异步收尾链路表(active/failed/pending 超24h 的金额处理);未实现 queryStatus 会停在开通中、24h 落 status=7
  • 上游分组与第三方系统对接:扣款与退款流程重写为同步/异步分通道(同步先开通后扣款、失败不扣款;异步 pending 扣款、failed 自动退款、超24h 不退款转人工)
  • 发布机制:min_version 口径明确为授权站更新提醒版本门槛(不校验安装),正式/内测分线校验
  • 数据库表结构:扩展表补三个合法持久化落点;产品分组「对接插件」过滤口径修正(只按 config_mode=group,与分组类型/apply_mode 无关)
  • 系统介绍:版本标注读法明确「自 X 版本起」默认指内测版,正式版 V1.2 尚未追上

V2.11(2026-11-02,V1.3.3.5.8 系统增强同步)

  • 插件开发指南:动作派发矩阵更新——代理插件分组用户端 renew / upgrade / 动作通道自 V1.3.3.5.8 起正常派发分组插件(V1.3.3.5.7 及更早仅本地处理)
  • 插件开发指南:开通回写 plugin_result 刷新语义更新——renew / upgrade 成功后同样刷新(upstream / instance_info 浅合并进 config 顶层,plugin_result 整键覆盖,无相关键不写库)
  • 控制面板开发:panelApi 支持静态 / 实例双风格(V1.3.3.5.8 起,系统按 ReflectionMethod::isStatic() 判定;实例风格构造函数注入合并后的 config,与 hostAction 同口径)

V2.10(2026-11-02,插件问题单核对修订)

  • 插件开发指南:plugin.json 字段表补充 min_version(权威位置为开发者中心发布表单,手填优先、包内字段仅表单留空时回退;require_version 为旧名兼容);「未知字段系统一律忽略」补充 min_version/require_version 例外说明
  • 插件开发指南:操作类型 upgrade 口径修正(弹性升级与产品类型无关,host/vps/dedi/cdn 均派发),新增「动作派发矩阵(产品配置通道 × 操作)」——如实标注代理插件分组在用户端 renew/upgrade/动作通道暂不派发(已知待完善项)
  • 插件开发指南:开通回写 plugin_result 补充刷新语义(仅 create 写入;renew/upgrade 不刷新;重装换上游 ID 需插件自行同步)——该口径已被 V2.11 取代(V1.3.3.5.8 起 renew/upgrade 同样刷新)
  • 插件开发指南:新增「测试节点调用契约」(yuxin_upstream 专用通道;其他代理插件走实例 hostAction('test');静态 testConnection 不会被调用)
  • 插件开发指南:install.sql 章节与最小骨架示例改用 CREATE TABLE IF NOT EXISTS,补充重装幂等说明;FAQ 8 建表失败条目同步补充
  • 上游分组与第三方系统对接:测试节点步骤补充调用契约引用;「对接插件」下拉口径确认只按 config_mode=group 过滤,apply_mode 在保存时按分组开通模式互斥校验(冲突自动清空插件)
  • 明确「自营受控端」界定:专指官方自研 kvm-agent,不含第三方虚拟化平台(PVE / Proxmox、ESXi、Hyper-V、OpenStack、Xen 等)——对接第三方虚拟化平台属对接类(config_mode=group + apply_mode 2/3,经上游分组挂载),不能按自营(apply_mode=1)发布;「插件上架与销售」经营方式口径同步收紧
  • 插件开发指南:type 字段补「不做封闭枚举校验不构成接入授权」说明;用户中心插件控制器补「写作防护责任」(系统无 CSRF token 校验且无登录鉴权兜底,插件必须自行校验登录态并对写操作做 CSRF/来源校验)
  • 模板/插件发布与更新机制:打包自检补「开通流程是否直连第三方虚拟化平台 API」自查项(是则按对接类发布)

V2.9(2026-11-02)

  • 系统介绍新增「正式版与内测版」章节:双线定义与发布节奏(正式版每周三打包更新、只含内测已验证稳定集合;内测版随改随发),开发者选线建议;文档版本号示例更新为 V2.9
  • 系统在线更新机制:三通道改为两通道(预览通道已全面下线),补充正式/内测发布节奏说明
  • 插件开发指南:config_params 控件类型修正为七类(text/password/number/textarea/select/switch/radio,原文「六类」笔误);配置合并顺序补全三层(插件全局 → 上游分组 → 产品,产品优先级最高)
  • 插件可配置参数:参数类型表补充 password 控件
  • 上游分组与第三方系统对接:修正「对接插件」下拉口径(仅列出 config_mode=group 的对接插件,不按产品类型过滤),新增「适用产品类型」多选说明(一个分组可服务多类型产品)
  • 插件开发指南新增「密码与密钥存储约定」:服务密码 / API app_secret 落库 AES-256-CBC 加密(enc: 前缀),读取时系统自动解密,插件无需自行加密
  • 模板开发指南:修复历史批量转义残留(字面反斜杠还原为真实换行、PHP 命名空间恢复单反斜杠);插件开发指南 / 更新日志同步清理字面换行
  • 模板/插件发布与更新机制:min_version 兼容闸补充正式版 / 内测版版本线隔开校验说明;示例名称中性化(your_theme / your_plugin)
  • 插件开发指南:hostAction / queryStatus 入口统一补齐 $host['type'] 服务类型判据(host 旧表行原缺该字段,插件现可用 $host['type'] 区分 host/vps/dedi/cdn 产品类型,对应内测版 V1.3.3.5.7);新增「状态查询 queryStatus」与「可选能力调用约定」章节(异步开通 instance_info 落 config.upstream;listOptions / listPeriods / listRegions / listImages 调用时机与入参)
  • 插件开发指南 plugin.json type 字段补充口径:对接类插件(config_mode=group)服务范围以所属分组「适用产品类型」为准,type 仅作标识与自营模式过滤

V2.8(2026-10-29)

  • 模板开发指南新增「时间格式化标准写法」章节:模板输出日期统一用 {$var|date='Y-m-d'} 写法(单引号参数、不带 ,###),引擎自动兼容时间戳与日期字符串(is_numeric($val) ? $val : strtotime($val))
  • 明确禁止 {$var|date="Y-m-d",###} 带 ### 占位符写法:模板引擎内置 date 修饰符不会替换 ###,编译产物残留 ### 会触发 PHP 语法错误(ParseError: unexpected token ";"),页面直接 500
  • 模板自检清单新增条目:时间日期输出统一用 {$var|date='Y-m-d'} 写法(单引号、不带 ,###)

V2.7(2026-10-29)

  • 开发文档页面重构:消除左右大空白(1440px 阅读容器)、分类 Tab 吸顶、目录滚动高亮(scrollspy)、移动端适配;页面 CSS/JS 全部外置到专属文件(pages/developer/docs.css、docs.js),不再内联
  • 新增「模板/插件发布与更新机制」专章:更新提醒只推给已安装用户、包内清单 name 必须与市场商品标识一致(换名 = 新商品,老用户收不到更新)、developer 决定安装目录且同一开发者多套模板/插件填同一标识(示例均为中性占位符)、min_version 兼容闸规则、版本号递增、发布新版本流程
  • 修正 template.json 的 min_version 字段说明(原描述与真实机制不符):实际为授权站更新提醒兼容闸,客户端安装/更新不校验
  • 插件上架与销售:版本管理说明扩写(新版本只推已安装用户)
  • 常见问题:第 5 条补充发布新版本流程,新增 FAQ「发布新版本后为什么用户收不到更新提醒?」
  • 系统在线更新机制:下线预览版通道说明(预览通道已全面下线,仅正式版/内测版)

V2.6(2026-10-29)

  • 模板开发指南「模板变量」重写:新增「场景 × 变量」注入矩阵(前台 home / 用户中心 user),明确标注 $controller / $action / $config 自 V1.3.3 起前台公共注入;补充 $config 安全键名清单与「密钥类配置不注入」安全说明
  • 新增「安全写法」:所有变量可能不存在,给出 |default=''、{notempty}、{if condition="!empty($controller)…"} 推荐写法,避免第三方模板白屏
  • 新增「路由口径」表:/product、/product/:type/:id、/product/group/:id、/news、/news/:id 官方推荐写法,明确 /article、/product/host/3 等非官方写法会 404
  • 新增「站内接口契约」:下单 POST /user/order/buy(product_id/period_id/domain/host_password/user_config)、预生成主机名 GET /product/purchaseHost、续费/升级/服务操作/面板操作/登录注册接口与参数、code=0 但 data.plugin_success=false 响应约定、半年付站内与 API 口径差异
  • 新增「公共 JS API」:window.Yx.request/get/post、window.YxDialog.alert/confirm/toast 稳定公开范围,说明 window.alert 已被系统重写
  • template.json 字段表新增 min_version(最低兼容系统版本,安装/更新校验,不填不校验)
  • 模板文件清单路由列修正:产品详情 /product/:type/:id、文章列表 /news、详情 /news/:id
  • 新增「局部文件命名规范」:下划线前缀 _xxx.html 允许且不会被当页面路由
  • 新增「HTML 注释勘误」:模板引擎不跳过 HTML 注释,注释内写模板标签会被编译,禁止在注释内书写 {include}/{extend}
  • 新增「模板自检清单」:BOM/内联样式/静态资源/变量判空/引号规范/必需文件六项上传前必查
  • 新增「版本兼容说明」:明确 $controller/$action/$config 仅 V1.3.3+ 注入,兼容写法=JS URL 高亮 + 变量判空(V1.2 亦可用),强依赖模板声明 min_version 1.3.3

V2.5(已更新第3次,2026-10-25)

  • 修复章节重复渲染:KVM受控端对接页误塞入九/十/十一章内容导致重复展示,已截断清理
  • 模板开发指南新增 template.json 字段说明表:6个字段类型/必填/取值说明,附完整JSON示例
  • 模板开发指南新增 模板文件清单:前台10个必需文件 + 用户中心12个必需文件,标注对应路由与必需性
  • 补充缺文件行为说明:找不到对应视图时自动回退官方默认模板,不报错
  • 模板文件清单补全:新增 user/invoice/index.html(发票管理)、user/apikey/index.html(API密钥管理)、user/notice/index.html(系统通知)三行
  • 新增「产品详情页子模板」说明:按产品 type 自动匹配 detail_host/detail_vps/detail_dedi/detail_cdn,未提供时回退通用 detail.html
  • 「必需」列表头改为「建议提供」,与缺文件静默回退行为对齐,避免语义矛盾
  • template.json 示例改用中性占位符(your_id/你的署名),官方保留标识在字段说明中标注
  • 目录结构注释改为指向字段表,避免两处维护不同步
  • 视图匹配路径措辞改为「场景/控制器/方法.html」,明确 home/user 为场景目录
  • 修复第五章代码块换行被字面化问题(114处字面 恢复为真实换行),template.json示例改用中性占位符

V2.4 (2026-10-24)

  • 新增「插件后台管理页(可选)」契约:controller/Admin.php + 统一入口 /admin/plugin/manage
  • 写清 install.sql 表前缀规则与卸载脚本 uninstall.sql/uninstall.php
  • 明确类加载约定与生命周期(不回调 onEnable/onDisable)
  • 用户中心插件控制器统一取登录用户 Session::get('user')['id']
  • plugin.json 字段表补 config_mode、apply_mode
  • 新增「财务与余额」一节:用户余额 user.money、流水表 finance_log、佣金流水 commission_log
  • 写清 panel.template 取值规则与 panelApi 返回口径;新增「最小可运行插件骨架」
  • 开发文档后台化改造:内容存数据库,后台富文本可编辑,支持图片上传

V2.3 (2026-10-21)

  • 服务台账数据模型更新:全类型服务统一以主表 service_main 为资源定位根
  • 补充类型专属 1:1 扩展表说明:service_vps、service_dedi、service_cdn
  • 订单明细表 order_item 读侧说明:service_id 优先,host_id 回退
  • 开通回写按类型区分:vps/dedi/cdn 落主表 config,虚拟主机落 host.config

V2.2 (2026-10-21)

  • 补写上游对接四块可选契约:listProducts、listPeriods、listImages、queryStatus
  • 重写 API 响应码说明:业务码统一 0成功/1失败,鉴权层 401/403/429
  • 产品类型枚举为 host/vps/dedi/cdn,主机状态七态
  • 面板契约统一为静态 panelApi($node, $module, $action, $params)

V2.1 (2026-10-07)

  • 新增「用户中心挂载点(user_mount)」插件契约
  • 插件结构补充 controller/User.php 与 view/user/ 目录
  • 模板开发指南更新:$activeMenu 高亮菜单标识、$pluginMounts 挂载区块变量

V2.0 (2026-09-27)

  • 新增用户等级/会员体系API文档
  • 新增 user_level 数据库表结构说明

V1.9 (2026-09-20)

  • 新增SSH终端功能说明
  • 新增系统在线更新机制说明
  • 三通道更新机制(正式版/内测版/预览版)

V1.8 (2026-09-09)

  • 新增「对接插件选项契约(listOptions)」

V1.7 (2026-09-08)

  • 新增上游分组与第三方系统对接章节
  • 新增自研 KVM 受控端对接章节
  • 更新插件开发指南:两种插件风格(静态 nodeAction / 实例 hostAction)
  • 更新数据库表结构:新增 server_group 表