语信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,运维面板任选,系统本身不绑定特定面板)
正式版与内测版
语信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
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/ # 第三方库
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 # 插件说明
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() 读取参数即可。
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']];
}
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 | 收款账号信息 |
| status | 0=待审核,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;
}
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": "概览"}
]
}
}
配置字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| name | string | 插件标识(唯一),同时决定插件安装目录(安装目录 = plugins/plugins/{name}/,目录名、plugin.json 的 name、命名空间三者一致)。插件不需要 developer 字段——developer 是模板 template.json 的约定(模板安装目录按 developer/模板名 分目录),插件安装目录与更新匹配一律由 name 决定 |
| title | string | 插件名称 |
| version | string | 插件版本 |
| author | string | 插件作者署名(开发者本人 / 工作室名称,由发布者自行填写;系统不内置官方作者、不提供默认署名,也不强制统一) |
| type | string | 插件分类标识:资源类 host(虚拟主机)/ vps(云服务器)/ cdn(CDN)/ dedi(独立服务器),声明插件主类型:自营模式(插件直接挂载产品)按此过滤可挂载的产品类型;对接类插件(config_mode=group,经上游分组挂载)的服务范围以所属分组的「适用产品类型」为准,type 不构成服务范围限制,仅作标识与自营模式过滤;其余为功能类(如 sms 短信、realname 实名、recharge 充值、marketing 营销、finance 财务、tool 工具等),按插件自身用途填写,系统不做封闭枚举校验(「不做封闭枚举校验」仅表示系统不校验 type 取值,不构成接入授权;type 只是分类标识,服务范围与货源方式由 config_mode / apply_mode 决定——第三方虚拟化平台请按对接类发布) |
| description | string | 插件描述 |
| config_params | array | 可配置参数列表 |
| panel | object | 控制面板声明(enabled/title/template/tabs),所有类型插件需要自建面板时均可声明,数据走 panelApi 分发 |
| user_mount | object | 用户中心挂载点声明(将插件功能挂载到用户中心对应页面,见下方小节) |
| config_mode | string | 配置归属模式,可选 global(默认,插件全局配置,在后台「插件管理」点配置填写)或 group(代理/上游插件,配置移到「上游分组」里选择本插件后填写 API 参数,插件管理页不再显示配置按钮)。对接某上游云/面板、需要按上游分组分发时填 group |
| apply_mode | int | 适用范围,可选 1=仅自营(直售)、2=仅代理分销、3=通用(默认,自营与代理都可用)。缺省按 3 通用处理。字段值与派发矩阵的对应:1=仅能被「自营直挂 / 自营分组」通道选中、2=仅能被「代理插件分组」通道选中、3=两种通道均可选中。apply_mode 只决定该插件能否被某配置通道选中,不决定动作如何派发——动作派发由产品配置通道(plugin_id / 分组 plugin_name)决定,见「动作派发矩阵」 |
| min_version | string | 最低兼容系统版本(可选,留空=不限制)。权威位置是开发者中心发布表单:发布时手填优先,本字段(或旧名 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 | 如个人数据、快捷功能类 |
插件侧实现
插件提供用户中心控制器 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
{
// 实现插件逻辑
}
}
操作类型说明
| 操作类型 | 说明 |
|---|---|
| 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 实测):
| 产品配置通道 | create | renew | upgrade | suspend / unsuspend / resetpwd / login | terminate(到期删除) |
|---|---|---|---|---|---|
自营直挂插件(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 等)
| 动作 | 同步失败(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,否则开通结果永远无法自动收尾。
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' => [] // 返回数据
];
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>
/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_version | PHP版本 | select | 是 | 是 | 82 |
| db_version | 数据库版本 | select | 是 | 否 | 57 |
| db_charset | 数据库字符集 | select | 否 | 否 | utf8mb4 |
| create_db | 创建数据库 | radio | 是 | 否 | 是 |
| create_ftp | 创建FTP | radio | 是 | 否 | 是 |
| 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.html | VPS购买页(弹性规格、镜像选择) |
| dedi(独立服务器) | home/product/detail_dedi.html | 独立服务器购买页 |
| cdn(CDN) | home/product/detail_cdn.html | CDN购买页 |
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 表现一致,可作参考模板
时间格式化标准写法
模板中输出日期/时间,统一使用系统模板引擎内置的 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=®ion_id= 地区筛选 |
| 产品分组 | /product/group/:id | 按分组查看产品 |
| 产品详情 | /product/:type/:id | type 取值 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(英文) | 含义 | 子模板 |
|---|---|---|---|
| xunizhuji | host | 虚拟主机 | detail_host.html |
| yunfuwuqi | vps | 云服务器 | detail_vps.html |
| cdn | cdn | CDN | detail_cdn.html |
| dulifuwuqi | dedi | 独立服务器 | detail_dedi.html |
站内接口契约(用户中心提交操作)
以下接口为对外稳定契约,模板/插件均可调用(版本间保持兼容):
| 操作 | 接口 | 参数 |
|---|---|---|
| 登录 | POST /doLogin | username、password |
| 注册 | POST /doRegister | username、email、password、repassword、ref、invite_code |
| 下单 | POST /user/order/buy | product_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/doRenew | doRenew 参数:id、period_id |
| 升级 | GET /user/service/upgrade?host_id= + POST /user/service/doUpgrade | doUpgrade 参数以升级页表单为准(配置档位) |
| 服务操作(暂停/恢复/重置密码/登录) | POST /user/service/action | id(服务ID)、act(login/resetpwd/suspend/unsuspend) |
| 面板操作 | POST /user/service/panelApi | host_id、module、action、params(JSON 字符串) |
| 删除服务 | POST /user/service/delete | id |
code=0 但 data.plugin_success=false 表示「订单已创建、服务开通失败」,模板必须展示 data.plugin_msg 给用户,不要直接当成功处理。
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}
{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 语法错误)。
{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(具体操作);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。
核心流程
- 安装对接插件:在后台「插件管理」安装并启用对接第三方系统的插件(如第三方对接插件),在插件配置页填写 API 站点、API Key 等基础信息;或在「上游分组」中新建分组时选择对接插件,直接在该分组填写 API 配置(一个对接插件可对接多个站点,各分组独立配置)
- 添加上游分组:后台「产品系统 → 上游分组」新建分组,选择对接插件后填写该站点的 API 地址 / Key / Secret 等配置项(按插件声明的 config 显示,不再显示通用 JSON 高级配置)
- 测试节点:在分组操作中点「测试节点」,验证与上游 API 连通性(返回成功后才可用于产品关联;实际调用方法见「插件开发指南 · 测试节点调用契约」)
- 添加产品:后台「产品管理 → 添加产品」,选择所属分组后选择货源方式(自营 / 代理),代理时下拉选择上游分组,并可通过「一键获取」拉取上游地区的产品列表、系统镜像、弹性配置等,选择上游产品后自动联动填入产品配置
- 购买联动:前台购买页按产品类型渲染(服务器 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 等),配置保存在该分组内,多个站点可各填一套 |
| 状态 / 排序 | 启用状态与显示顺序 |
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 | 年付 | 标准周期 |
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)转人工排查 |
- 同步通道(create 直接返回结果):系统先开通、后扣款——开通成功才扣款并置订单已支付(pay_status=1);开通失败(code=1)不扣款,订单保持待支付(pay_status=0,可删除)、服务置开通失败(status=6)、remark 注明「服务开通失败,未扣款」。
- 异步通道(create 返回 pending):上游订单已支付、实例异步开通中,系统置服务开通中(status=5)并按订单金额扣款;此后周期轮询
queryStatus——返回active则置开通成功(status=1);返回failed则置开通失败(status=6)并自动退款(把订单实付金额原路退回用户余额,写入finance_logtype=refund 与service_logaction=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折 |
后台配置
管理员可在「用户等级管理」中:添加/编辑/删除等级、设置升级条件(累计消费金额或累计下单数量)、设置折扣率、设置等级排序和状态。
用户中心展示
- 显示当前等级图标和名称
- 显示累计消费金额
- 显示距离下一级还需消费多少
- 显示升级进度条
核心表
{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_cdn | CDN 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 | 系统更新历史表 |
产品表结构
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 产品ID |
| group_id | int | 分组ID |
| plugin_id | int | 关联插件ID(自营 / 对接插件) |
| server_group_id | int | 上游分组ID(代理货源时关联,0=自营) |
| upstream_provider_id | int | 上游供应商ID(对接第三方时) |
| upstream_product_id | string | 上游产品ID(对接第三方时,一键获取上游产品后自动填入) |
| name | string | 产品名称 |
| type | string | 产品类型:host(虚拟主机) / vps(云服务器) / dedi(独立服务器) / cdn(CDN) |
| description | text | 产品描述 |
| config | json | 产品配置(弹性规格、默认镜像等;代理时可一键同步上游最低配置) |
| status | int | 状态:1启用,0停用 |
| is_recommend | int | 是否推荐 |
| sort | int | 排序 |
| create_time | int | 创建时间 |
| update_time | int | 更新时间 |
通用服务台账主表 service_main 结构(资源定位根)
系统全部服务类型(host 虚拟主机 / vps 云服务器 / dedi 独立服务器 / cdn)的生命周期统一落在主表 service_main;类型专属的定位/规格等属性存放在 1:1 扩展表(service_vps / service_dedi / service_cdn),其余未列化的开通参数与上游回写仍存 config JSON。虚拟主机的 domain/username/password 等专属属性仍在 host 表。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 主表服务ID(系统内部资源定位主键) |
| user_id | int | 所属用户ID |
| agent_id | int | 归属代理ID |
| product_id | int | 产品ID |
| server_group_id | int | 上游分组ID(代理货源服务) |
| type | string | 服务类型:host / vps / dedi / cdn |
| legacy_host_id | int | 旧 host.id 映射(仅 host 虚拟主机与双写期历史记录使用;vps/dedi/cdn 新单为 0) |
| config | json | 开通配置快照 + 未列化专属属性;开通成功后系统回写 plugin_result(含上游资源ID/IP等) |
| status | int | 服务状态:1正常,2已暂停,3已到期,4已删除,5开通中,6开通失败,7开通异常(超时待人工排查) |
| remark | string | 客户自用备注(仅本人可见) |
| expire_time | int | 到期时间 |
| create_time | int | 创建时间 |
| update_time | int | 更新时间 |
hostAction / queryStatus / panelApi,插件仍按原 $host 数组与 config.plugin_result 取资源ID,无需插件作者感知底层表拆分。
虚拟主机专属表 host(仅 type=host)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 虚拟主机记录ID(即主表 legacy_host_id) |
| domain | string | 绑定域名 |
| username | string | 虚拟主机用户名 |
| password | string | 虚拟主机密码 |
| server_group_id | int | 上游分组ID(代理货源服务) |
| config | json | 虚拟主机配置 + plugin_result 回写 |
| status | int | 服务状态:1正常,2已暂停,3已到期,4已删除,5开通中,6开通失败,7开通异常 |
| expire_time | int | 到期时间 |
| create_time / update_time | int | 创建/更新时间 |
类型专属扩展表(1:1,service_id 关联主表)
| 扩展表 | 承载类型 | 主要列化字段 |
|---|---|---|
| {prefix}service_vps | vps 云服务器 | service_id、vm_id(上游实例ID)、ip、username、password、ipv4(全部IPv4)、image_os(镜像/系统) |
| {prefix}service_dedi | dedi 独立服务器 | service_id、sn(序列号)、ip、bandwidth(带宽)、room(机房) |
| {prefix}service_cdn | cdn(预留) | 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)的接口,也不保证直写结果会被读取。
产品分组表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 分组ID |
| name | string | 分组名称 |
| type | string | 分组类型:host(虚拟主机) / vps(云服务器) / dedi(独立服务器) / cdn(CDN) |
| sort | int | 排序 |
| status | int | 状态 |
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)去授权站市场匹配的:本地装过哪个商品,才检测哪个商品的更新。因此:
- 发布新版本 = 更新该商品的最新版本包,只会让已经购买并安装该商品的用户收到更新提醒;
- 全新商品(从未被任何人安装过):用户需要先到市场购买、到系统后台「系统商店」安装,安装后才进入更新检测;
- 不存在「发布新版本 = 全站用户都能看到提醒」的广播机制。
红线:包内清单的 name 必须与市场商品标识完全一致
安装包根目录下的 template.json(模板)/ plugin.json(插件)中,name 字段必须与授权站市场商品的「标识」完全一致(即发布产品时填写的商品标识,如 your_theme、your_plugin)。
安装目录规则:模板由 developer 决定,插件由 name 决定
模板安装目录 = view/template/开发者标识/模板名/,开发者标识取 template.json 里的 developer 字段(字母+数字/下划线/连字符,author 是署名、不参与目录)。插件安装目录 = plugins/plugins/插件标识/,由 plugin.json 的 name 字段决定,插件清单不需要 developer 字段(插件更新匹配同样按 name)。
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 才会收到该新版;版本不足的客户端回落到低门槛的旧版本包(或提示不可更新) |
说明:min_version 只影响授权站市场的更新提醒过滤;客户端安装/更新时不会校验该字段。
版本号规则
- 版本号格式
主.次.修订(如1.0.0→1.0.1),每次发布必须递增,不能重复或回退; - 系统侧按
version_compare比较版本号判断是否有更新,版本号写错会导致「检测不到更新」或「反复提示更新」。
发布新版本操作流程
- 进入开发者中心 → 我的插件 / 我的模板 → 找到商品点「版本」;
- 点击「发布新版本」:填写递增的版本号、更新说明(用户侧会展示)、上传该版本安装包(zip);
- 如需限制最低系统版本,填写 min_version(不确定请留空)。min_version 的作用是限制授权站客户端「检测到更新」的版本门槛:客户端系统版本低于该值时,不提示该版本更新(客户端上报
system_version匹配);它不会在安装 / 更新时做强制校验,更不是「不满足就不能安装」的硬闸。正式版与内测版按版本线分别校验:正式版只认正式版号、内测版只认内测版号(例如 min_version 填 V1.3.3.5.8 只对同线版本生效)。想全兼容请留空; - 提交后版本进入版本列表,已安装该商品的用户系统即可检测到更新。
1. 如何开发一个新插件?
- 在
plugins/plugins/目录下创建插件目录 - 创建
plugin.json配置文件 - 创建插件主类,实现
nodeAction(静态风格)或hostAction(实例风格,推荐)方法 - 如需自建控制面板:在 plugin.json 声明
panel(enabled/title/template/tabs),并实现panelApi($node, $module, $action, $params)(静态或实例方法均可,内测版 V1.3.3.5.8 起支持实例风格,正式版 V1.2 及此前仅静态风格)处理面板数据 - 在后台插件管理中安装并启用插件
2. 插件的可配置参数如何使用?
在 plugin.json 中定义 config_params,设置 user_selectable 为 true 的参数可以让用户在购买时选择。用户选择的值会保存在服务配置中,开通服务时传递给插件。
3. 如何对接第三方系统?
- 安装对应的对接插件(插件适配系统,遵循 PluginManager 契约,系统核心不因第三方改动)
- 后台「产品系统 → 上游分组」新建分组,选择对接插件并填写该站点的 API 地址 / Key / Secret
- 点击「测试节点」验证连通性
- 「产品管理 → 添加产品」选择所属分组、货源方式(代理)与上游分组,通过「一键获取」拉取上游产品/镜像/弹性配置并联动填入
4. 插件开通服务失败怎么办?
- 检查插件配置是否正确
- 检查上游分组节点连接是否正常(后台上游分组中测试节点)
- 查看错误日志,定位问题
- 资金口径:同步开通先开通后扣款、失败不扣款;异步开通 pending 已扣款,failed 自动退款,超 24h 不退款转人工(详见「扣款与退款流程」)
- 开通失败的订单和服务支持用户删除
- 联系插件开发者获取支持
5. 开发者如何发布插件/模板?
- 在授权站注册账号,申请成为开发者
- 进入开发者中心,点击「发布产品」
- 选择产品类型(插件/模板/服务),选择分类与经营方式(自营 / 代理对接)
- 填写产品信息,上传源码压缩包
- 提交审核,等待管理员审核通过
- 审核通过后前台展示,用户购买后在系统后台「系统商店」下载安装
- 已发布的商品发布新版本:开发者中心 → 我的插件/我的模板 → 找到商品 → 「版本」→「发布新版本」,填写递增版本号与更新说明并上传新包(详见「模板/插件发布与更新机制」章节)
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. 发布新版本后为什么用户收不到更新提醒?
最常见的原因有三种:
- 该用户还没有安装你的商品——更新检测只匹配本地已安装的商品标识(name),全新商品需用户先到市场购买安装;
- 包内清单 name 与市场商品标识不一致——系统按包内 name 安装并记录,之后按包内 name 去市场匹配,永远匹配不到你的商品(发布新版本前务必核对一致);
- min_version 高于用户系统版本——授权站按客户端上报的系统版本过滤更新提醒,版本不足会回落旧版或提示不可更新(想全版本兼容就留空或填 ≤ 目标旧版本)。
概述
自研受控端(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 | 删除虚拟机 |
系统内置Web SSH终端功能,用户无需安装第三方SSH工具,直接在浏览器中即可连接和管理云服务器。
功能特点
- ✅ 零配置:无需安装额外软件,浏览器直接使用
- ✅ 安全:通过系统统一认证,密码不明文传输
- ✅ 易用:支持命令复制粘贴、快捷键操作
- ✅ 兼容:支持所有标准Linux命令
使用说明
- 用户在「我的服务」中找到已开通的云服务器
- 点击「管理面板」进入控制面板
- 切换到「SSH终端」标签页
- 点击「连接」按钮,即可开始命令行操作
技术说明:系统使用phpseclib3库实现SSH连接,采用HTTP长轮询方式,无需额外配置WebSocket服务,兼容所有PHP环境。
相关API接口
| 接口 | 方法 | 说明 |
|---|---|---|
/user/service/sshExec | POST | 执行SSH命令 |
IDC财务系统提供API接口,供第三方系统对接资源池,实现产品查询、订单创建、主机管理、账户查询等功能。
IDC财务系统API对接接口
IDC财务系统提供以下API接口,供第三方系统对接资源池,实现产品查询、订单创建、主机管理、账户查询等功能。
http://your-domain.com/api 数据格式:JSON 字符编码:UTF-8 请求方式:GET / POST
认证方式
所有API接口都需要进行身份认证。用户需要在语信IDC财务系统的用户中心 → API密钥管理中创建API密钥,获取 app_key 和 app_secret。
三个认证参数既可放在请求头(X-App-Key / X-Timestamp / X-Sign),也可直接放在请求参数(app_key / timestamp / sign)中,中间件优先取请求头。
请求头参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| X-App-Key | string | 是 | API密钥的app_key |
| X-Timestamp | int | 是 | 当前时间戳(秒),与服务器时间差不能超过300秒 |
| X-Sign | string | 是 | 请求签名,详见签名规则 |
签名规则
为了保证接口调用的安全性,所有请求都需要进行签名验证。签名算法如下:
- 将所有请求参数(除 sign 外)按参数名的字母顺序升序排列
- 将排序后的参数拼接成
key1=value1&key2=value2&...的格式 - 在字符串末尾拼接
&app_secret=你的app_secret - 对最终字符串进行 MD5 哈希,得到32位小写签名
- 将签名作为 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": { }
}
| 字段 | 类型 | 说明 |
|---|---|---|
| code | int | 状态码,0表示成功,非0表示失败 |
| msg | string | 提示信息 |
| data | mixed | 返回数据,可能是对象、数组或空数组 |
响应码说明
接口响应分为业务响应码(控制器返回,所有接口统一)与鉴权层状态码(中间件在签名 / 密钥校验阶段返回)两层,不要混用。
1. 业务响应码(正常进入接口后返回):
| code | 含义 | 说明 |
|---|---|---|
| 0 | 成功 | 业务处理成功,数据在 data 中 |
| 1 | 业务失败 | 具体原因以 msg 为准,常见:产品不存在或已下架、订单不存在、主机不存在、余额不足、购买/续费周期不正确、域名格式错误等 |
2. 鉴权层状态码(未通过中间件校验时返回):
| code | 触发条件 | 处理建议 |
|---|---|---|
| 401 | 缺少 app_key / timestamp / sign;时间戳格式错误;与服务器时间差超过 300 秒;API密钥不存在;签名验证失败 | 检查三个认证参数是否齐全、时间戳是否为当前秒级、签名算法与参与签名的参数是否正确 |
| 403 | API密钥已被禁用;或当前授权版本不支持 API(需专业版 / 企业版) | 到用户中心启用密钥,或联系管理员升级授权版本 |
| 429 | 调用频率超限(超过该密钥每小时配额) | 稍后重试,或联系管理员提升每小时配额 |
产品接口
产品列表 GET /api/product/list - 获取可购买的产品列表
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 页码,默认1 |
| limit | int | 否 | 每页数量,默认10 |
| type | string | 否 | 产品类型:host(虚拟主机)、vps(云服务器)、dedi(独立服务器)、cdn(CDN) |
| group_id | int | 否 | 产品分组ID |
产品详情 GET /api/product/detail - 获取单个产品的详细信息
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | int | 是 | 产品ID |
产品分组 GET /api/product/groups - 获取产品分组列表
订单接口
订单列表 GET /api/order/list - 获取当前用户的订单列表
订单详情 GET /api/order/detail - 获取单个订单的详细信息
创建订单(购买产品) POST /api/order/create - 创建订单并购买产品,先开通后扣款——开通成功才扣款,开通失败不扣款。接口同步返回,内部同时支持同步 / 异步开通:插件返回 pending 时 host_status=5(开通中),异步 failed 自动退款、超 24h 不退款转人工。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| product_id | int | 是 | 产品ID |
| duration | string | 是 | 购买周期:month(月付)、quarter(季付)、year(年付) |
| domain | string | 条件必填 | 绑定域名,虚拟主机类型产品必填,支持一级域名、二级域名、泛域名 |
| quantity | int | 否 | 购买数量,默认1 |
主机接口
主机列表 GET /api/host/list - 获取当前用户的主机列表
主机详情 GET /api/host/detail - 获取单个主机的详细信息,包括FTP、数据库等配置信息
主机操作(续费等) POST /api/host/action - 对主机进行操作,目前支持续费操作
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | int | 是 | 主机ID |
| act | string | 是 | 操作类型:renew(续费) |
| duration | string | 续费必填 | 续费周期:month(月付)、quarter(季付)、year(年付) |
账户接口
用户信息 GET /api/account/info - 获取当前API密钥关联的用户信息(含等级信息)
账户余额 GET /api/account/balance - 获取当前用户的账户余额
财务明细 GET /api/account/finance - 获取当前用户的财务明细记录
充值记录 GET /api/account/recharge - 获取当前用户的充值记录
用户等级/会员体系
| 接口 | 方法 | 说明 |
|---|---|---|
GET /api/account/info | GET | 返回用户当前等级、累计消费、升级进度等信息 |
返回字段说明:
| 字段 | 说明 |
|---|---|
| level_id | 用户等级ID |
| level_name | 等级名称(如:普通用户、白银会员、黄金会员、钻石会员) |
| level_discount | 等级折扣率(1.00=无折扣,0.95=95折) |
| total_consume | 累计消费金额 |
| next_level | 下一个等级信息(如有) |
| upgrade_progress | 升级进度(0-100%) |
系统采用全新的在线更新机制,支持正式版 / 内测版两通道发布,安全可靠的分步更新流程。
更新通道
| 通道 | 说明 | 适用场景 |
|---|---|---|
| 正式版 | 稳定版本,经过充分测试 | 生产环境用户 |
| 内测版 | 测试版本,包含新功能和修复 | 内测用户、插件开发者 |
更新流程
发布节奏:正式版非紧急情况每周三打包更新,内容只包含已在内测版验证稳定的改动集合;内测版随改随发(新功能与修复先行发布)。插件/模板开发者建议使用内测版联调新契约,生产环境请使用正式版。
- 检查更新:系统自动检测是否有新版本
- 下载更新包:下载更新包到本地临时目录
- 文件校验:校验更新包文件完整性和一致性
- 备份文件:自动备份需要覆盖的文件
- 解压覆盖:解压更新包,覆盖系统文件
- 数据库升级:自动执行数据库升级脚本,兼容跨版本升级
- 更新完成:清除缓存,完成更新
跨版本升级支持:系统支持从任意旧版本直接升级到最新版本,自动执行中间所有版本的数据库升级脚本,无需逐版本更新。
数据库自动升级
每个版本的更新包都包含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 表