XIUNOX 应用市场上架指南
开发者入驻 · 应用发布 · 审核规范 · 版本更新
1开发者入驻流程
访问 /appcenter-developer-edit.htm 提交资料,管理员审核通过后才能发布应用。校验入口 AppCenterService::checkPublishPrerequisite()。
1.1 必填字段
| 字段 | 校验规则 | 说明 |
|---|---|---|
intro | 必填,≤1000 字 | 开发者简介 |
contact_qq | 必填 | 主联系方式 |
contact_email / contact_wechat / contact_phone | 3 选 1 必填 | 备用联系方式 |
brand_prefix | 正则 ^[a-zA-Z]{5,}_[a-zA-Z]*$ | 5+ 字母开头 + 下划线;禁用 xn/xnx/xiuno/xiunox |
official_domain | URL,自动补 https:// | 开发者主页 |
portfolio_tid_1/2/3 | 必须是本人发布在作品版块(后台可配置 fid)且审核通过的帖子 | 3 个不同的作品帖子 |
agreement_agree | 必须为 1 | 协议需滚动到底 + 10 秒倒计时后才能勾选 |
1.2 审核流程
- 提交后
audit_status=0,管理员通过后置audit_status=1 - 审核通过后自动发介绍帖到
forum_fid配置版块(createDeveloperThread()),存developer.thread_tid避免重复发帖 - 未通过审核无法发布应用
2应用发布字段规范
发布入口 /appcenter-publish.htm,POST 到 route/appcenter.php publish action。
2.1 字段全表
| 字段 | 必填 | 校验规则 |
|---|---|---|
name | 是 | ≤9 字符 |
slug | 是 | 3–50 字符,必须以 brand_prefix 开头,全局唯一,发布后不可改 |
brief | 是 | ≤50 字 |
category | 是 | 后台预设分类(plugin/template/extension) |
tag_keys[] | 是 | 1–2 个功能标签 |
description | 是 | AIEditor 富文本,纯文本 ≥100 字 |
icon_tmp_key | 是 | 应用图标 |
screenshot_tmp_keys[] | 是 | 至少 1 张截图 |
applicable_version | 是 | 1.0 或 1.1 |
price_mode | 是 | v1.15.29 起三选一:free=免费 / credits=积分兑换 / cash=现金付费 |
price_type | — | 底层字段,由 price_mode 自动映射(free=0 / credits=1 / cash=1) |
download_type | — | 底层字段,由 price_mode 自动绑定(free/credits=1 官方市场 / cash=2 自托管),不可手动切换 |
price_credits_type | credits 时必填 | credits/golds(不含 rmbs,rmbs 已由 cash 模式专属) |
price_amount | credits/cash 时必填 | 受 max_price 上限限制;cash 模式另要求 ≥1 元 |
external_url | cash 时必填 | ≤255 字符,站外下载地址,域名需与开发者「官方网站」一致 |
version | 是 | 首版本号 |
version_download_tmp_key | 是 | 首版本下载包(dt=1 时必填) |
guide_read / scan_passed | 是 | 两个声明,必须为 1 |
2.2 上架前自检
- 插件包扫描(
PluginScanner):扫描 conf.json/install/uninstall/upgrade/hook 结构问题 - 上架前必看:本文档 iframe 嵌入发布页 Modal,必须点击"已阅读"
- 自检声明:发布页富文本编辑器,填写上架前自检报告(必填)
3应用状态机与审核流程
应用 status 字段共 4 个状态。
3.1 状态定义
| 值 | 含义 | 可见性 |
|---|---|---|
0 | 待审核 | 仅作者 + 管理员 |
1 | 已发布 | 所有人可见可下载 |
2 | 审核未通过 | 仅作者,reject_reason 必填 |
3 | 已下架 | 仅作者,reject_reason 必填 |
3.2 状态转换
| 当前 | 可转到 | 触发入口 |
|---|---|---|
| 0 待审 | 1 通过 / 2 拒绝 | 后台 audit_app |
| 1 已发布 | 3 下架 | 后台 audit_app(reason 必填) |
| 3 已下架 | 1 重新上架 / 0 退回审核 | 后台 republish_app / return_audit |
| 任意 | 0 待审 | 编辑提交(need_audit=true 或上传新版本强制回审) |
3.3 审核通过自动发帖
forum_fid > 0 且 status 变为 1 时,updateAppStatus() 自动发帖:
- 首帖(
createForumThread):app.thread_tid为空时触发,标题【XIUNOX{分类名}】{应用名} v{版本号},内容含描述+截图+下载链接(xnx_hidden 启用时回复可见) - 更新帖(
createUpdateThread,v1.15.0):app.thread_tid非空且最新 version 的thread_tid为空时触发,标题加"更新"后缀
发帖人固定为应用作者
$app['uid'],走 thread_create() 并传 skip_attach_assoc=true。4版本更新与自动发帖
v1.15.0 起两个入口逻辑统一:上传新版本安装包后版本号和更新日志必填,提交后强制回审,审核通过后自动发"更新帖"。
4.1 入口对比
| 入口 | 必填字段 | 回审 | 发帖 |
|---|---|---|---|
| 编辑页"上传新版本" | 上传包后:version_new + version_changelog | 是 | 审核通过后发更新帖 |
| 详情页"添加新版本" | version + changelog + 下载包 | 是 | 审核通过后发更新帖 |
| 编辑页"单纯编辑(不上传包)" | 无新版本字段 | 取决于 need_audit | 不发帖 |
| 审核驳回修改 | 无新版本字段 | 取决于 need_audit 或 status=3 | 不发帖 |
4.2 数据存储
bbs_xnx_appcenter_version:每版本一条,含version/download_aid/changelog/thread_tid(v1.15.0 新增) /create_timeapp.version:addVersion 时同步更新为最新版本号app.thread_tid:仅记录首帖 tid,更新帖 tid 在 version 表
设计原则:触发"上传新版本"流程的唯一标志是
version_download_tmp_key 非空(上传了安装包),不是 version_new 非空。只填版本号不上传包,会走"单纯编辑"分支被静默忽略。
5现金付费模式(自托管 download_type=2)
v1.15.29 起,自托管模式专用于「现金付费」:用户在开发者自己站点用人民币购买下载,平台不参与收款。下载时直接 302 跳转开发者提供的 external_url,不走站内购买流程。
5.1 约束
- 自托管专用于现金付费:
download_type=2必须由price_mode=cash自动绑定,前端禁用「免费 / 积分兑换」切到自托管 - 免费应用不可自托管:免费应用请直接发布到官方应用市场,无需跳转外站
- 积分兑换不可自托管:积分扣费只能在官方市场内闭环,自托管无法扣站内积分
- 必填下载地址(域名需与开发者「官方网站」一致)+ 测试账号 + 测试密码 + 现金金额(≥1 元)
- 详情页不渲染站内购买按钮,满足条件显示"前往下载"按钮,不满足显示"无法下载"并弹窗
- 详情页价格徽章仅显示"付费",不显示金额
5.2 下载按钮确认弹窗
| 场景 | 对应价格模式 | 弹窗颜色 | 标题 | 关键条款 |
|---|---|---|---|---|
| 免费应用下载 | 免费 (free) | 蓝色 | ⛑️ 安装前友情提示 | 个人维护、无深度安全检测、引导反馈 |
| 站内积分购买 | 积分兑换 (credits) | 黄色 | 💰 虚拟商品购买确认 | 积分即扣、概不退回、3 秒倒计时锁定按钮 |
| 跳转外站下载 | 现金付费 (cash) | 红色 | 🚨 即将离开本站 | 在开发者站点用现金购买,平台不参与收款、不抽成、概不退费,风险自担 |
6价格、抽成与下载权限
6.1 价格模式三选一(v1.15.29)
详细填写指南见 plugin/xnx_appcenter/docs/price_guide.md。
| 价格模式 | 下载方式 | 扣费渠道 | 谁收款 | 适用场景 |
|---|---|---|---|---|
| 免费 (free) | 官方应用市场 | 无 | 无 | 完全免费分享 |
| 积分兑换 (credits) | 官方应用市场 | 站内积分(金币/经验等) | 平台积分体系 | 站内积分购买,平台内闭环 |
| 现金付费 (cash) | 开发者自托管 | 人民币(元) | 开发者自己站点 | 开发者站点收款,用户去开发者站点购买下载 |
- 核心规则:三种模式互斥,下载方式由价格模式自动绑定,前端 JS 联动禁用冲突选项,不可手动切换下载方式
- 免费 + 自托管 / 积分 + 自托管:不允许。免费请走官方市场分发,积分扣费只能在官方市场闭环
- 现金付费收款方是开发者自己:平台不参与收款。详情页展示的收款二维码是开发者扫码支付审核测试费给管理员的(金额=现金价格),供管理员前往开发者站点体验一次完整购买下载流程
6.2 价格字段(底层映射)
price_mode:前端 radio,free/credits/cash 三选一price_type:底层字段,free=0 / credits=1 / cash=1(无需手动传)download_type:底层字段,free/credits=1 官方市场 / cash=2 自托管(前端联动禁用)price_credits_type:credits 模式必填,credits/golds(不含 rmbs,rmbs 已由 cash 模式专属),受allowed_credits_types限制price_amount:credits/cash 时必填,受max_price上限限制(checkPriceLimit());cash 模式另要求 ≥1 元
6.3 后台配置
commission_rate:系统抽成百分比,默认 10%(仅适用于官方市场内积分购买流水,现金付费平台不参与收款不抽成)max_price:全局最大价格default_rmbs_price:一键批量设置付费应用为人民币付费(仅用于存量迁移)
6.4 收益流水
bbs_xnx_appcenter_earnings 表记录每笔购买:order_amount / developer_income / system_fee / commission_rate。仅积分兑换模式产生流水(站内积分扣费抽成),现金付费不在此表记录(开发者自行收款)。
6.5 下载权限
download_allow_groups:允许下载的用户组(空=不限制)download_require_medals:需拥有任意一个勋章才能下载(数组)- 作者和管理员(gid=1,2)豁免
- 官方市场付费应用(积分兑换):必须先
PurchaseService::isPurchased()通过 - 自托管现金应用:不校验
isPurchased(),满足条件直接 302 跳转external_url
6.6 存量数据迁移
v1.15.29 前存量应用编辑时映射规则:
| 旧组合 | 新模式映射 | 处理方式 |
|---|---|---|
| 免费 + 官方市场 | 免费 | 自动映射 |
| 付费 + 官方市场 + 积分类型 | 积分兑换 | 自动映射 |
| 付费 + 官方市场 + 人民币(rmbs) | ⚠️ 需手动选择 | 编辑时显示迁移提示,开发者手动选「积分兑换」或「现金付费」 |
| 付费 + 自托管 + 人民币(rmbs) | 现金付费 | 自动映射 |
7代码规范与踩坑速查
所有上架插件必须遵守。详细规则见 .trae/rules/project_rules.md 与 bugfix_rules.md。
7.1 插件结构
conf.json:必含name/brief/version(X.Y.Z 三位制)/bbs_version(1.0 或 1.1,禁止 4.5);禁止包含installed/enableinstall.php:CREATE TABLE IF NOT EXISTS+DEFAULT CHARSET=utf8mb4uninstall.php:标准拼写(非unstall.php),DROP TABLE所有插件表 +setting_delete('插件名')upgrade.php:幂等(SHOW COLUMNS+ALTER TABLE),结构变更必走此机制- 覆盖核心模板放
plugin/<dir>/overwrite/,禁止改view/htm/ lib/下的类不会自动加载,路由层必须显式include
7.2 Hook 规范(高频踩坑)
禁止在 hook 文件中使用
return;:会从宿主函数/路由/模板返回,跳过后续逻辑。违规 5 次导致严重 bug。终止性 exit 需用 if 包裹并加 ponytail: 注释。闭包内 return 合法。
hook 注释禁止
// hook xxx 格式:编译器正则误匹配为 hook 占位符,多趟编译重复拼接导致 ParseError。改用 // hook: xxx 或 // xxx。违规 2 次。
- PHP hook 以
<?php开头(防直接访问) - 插件语言键写入
hook/lang_*_bbs.php,修改后清tmp/lang_*_bbs.php - Service 类内部含 hook 注释的必须通过
hook/model_inc_file.php注册,禁止直接include_once
7.3 安全 API
- 所有 POST 表单含
CsrfService::input() - 输出用
esc_html()/esc_attr()/esc_js(),禁止innerHTML拼接用户输入 param()默认转义,密码等敏感字段传第 3 参FALSE关闭转义- 密码存储
md5(md5(明文)+salt),登录用user_login_verify() - 验证码每次提交后刷新
7.4 缓存与编译
修改
_include() 文件后必须清 tmp/ 编译缓存:route/model/view/htm/lang 都通过 _include() 加载,编译缓存命名规则为完整路径的 / 替换为 _。_include() 只检查 tmpfile 是否存在,不比较源文件修改时间。违规 3 次。
- 插件模板
plugin/xnx_verify/view/htm/xxx.htm→tmp/plugin_xnx_verify_view_htm_xxx.htm - 语言包
lang/zh-cn/bbs.php→tmp/lang_zh-cn_bbs.php(连字符不是下划线) - 批量清理:
rm -f tmp/plugin_xnx_{插件名}_* tmp/lang_*.php
- 新增缓存用
CacheHelper::remember(),清理用CacheHelper::pluginDeletePrefix() CacheHelper::set()写版本号/裸值必须用array('__v'=>$value)哨兵格式- 禁止
cache_truncate用flushdb,改用deleteByPrefix - 缓存键
bbs_cache.k为varchar(255),长键正常容纳,无需哈希
PHP 8+ 中
@ 错误抑制符不捕获 Error:被 disable_functions 禁用的内置函数(chmod/mkdir/touch 等)调用会抛 Error 终止脚本,nginx 返回 502。必须用 function_exists('xxx') 守卫包裹。
7.5 数据与 SQL
display_name禁止 SELECT:是user_format()派生的虚拟字段,SQL 中不存在。用user_find_by_uids()批量取bbs_user无status:封禁用ban_type(0=正常 / 1=禁言 / 2=禁止访问 / 3=锁定)bbs_user无avatar_url:动态计算字段,SQL 中 SELECT 会触发Unknown columndb_*函数$table禁止含前缀:内部自动拼接,原生 SQL 需手动拼$db->tablepre- 分页过滤条件必须下沉 SQL,计数器递减用
user_dec/forum_dec/thread_dec(含GREATEST保护) - checkbox 必须
value="1":未声明 value 时默认提交 "on",intval("on")===0导致后端校验失败
7.6 前端与 htmx
- 禁用 jQuery、Alpine.js、idiomorph。DOM 操作改用原生 JS 或 htmx
- htmx 事件名必须用冒号格式:
htmx:config:request、htmx:after:swap、htmx:response:error。旧版驼峰格式(htmx:configRequest/htmx:afterSettle等)已移除,静默失效 - 后端 htmx 请求返回纯 HTML 片段,不返回完整页面、重定向或 JSON
hx-swap="none"时通过HX-Trigger响应头触发前端事件- 每个局部刷新元素必须明确
hx-target/hx-swap/hx-select - 异步加载大体积 JS 必须监听
script.onload+script.onerror,setTimeout 仅作兜底 - UI 改版时必须同步修改 JS 中的 DOM 选择器,grep 所有引用该选择器的 JS 文件
- PC/移动端双模板避免相同 id,移动端加
-mobile后缀 - 卡片用
x-cardclass,按钮用px-*控制宽度,禁止w-100 - 表单提交回调中禁止
jform.reset()(会清空用户输入) - JS/CSS 修改后递增
conf.json.version强制刷新浏览器缓存
7.7 其他常见陷阱
- HTML 注释
<!-- -->不防 PHP 执行:被注释包裹的<?php ?>仍会被执行。需要屏蔽 PHP 代码必须用 PHP 注释//或/* */ - Tabler Icons 必须用
<i class="ti ti-xxx"></i>:项目不存在tabler_icon()函数 - URL 函数禁止双重包裹:
admin_plugin_setting_url()等已内置url(),模板中直接echo - 判断用户是否默认头像禁止用
avatar_url字符串比较:头像替换类插件会改写avatar_url。正确做法:检查user['avatar']字段 + 物理文件存在性
8上架前自检清单
提交应用前逐项核对,"必检"项不通过直接拒绝上架。
8.1 文件结构
- conf.json 含 name/brief/version(X.Y.Z)/bbs_version(1.0 或 1.1),无 installed/enable
- install.php 用 CREATE TABLE IF NOT EXISTS + DEFAULT CHARSET=utf8mb4
- uninstall.php 标准拼写,DROP TABLE 所有插件表 + setting_delete
- upgrade.php 幂等(SHOW COLUMNS + ALTER TABLE)
- 覆盖模板放 overwrite/,禁止改 view/htm/
- lib/ 下的类必须显式 include
8.2 安全
- 所有 POST 表单含 CsrfService::input()
- 输出用 esc_html() / esc_attr(),禁止 innerHTML 拼接用户输入
- param() 敏感字段(密码)传第 3 参 FALSE 关闭转义
- 密码存储 md5(md5(明文)+salt),登录用 user_login_verify()
8.3 Hook
- hook 文件禁止 return;
- hook 注释禁止 // hook xxx 格式
- 终止性 exit 必须 if 包裹 + ponytail: 注释
- 插件语言键写入 hook/lang_*_bbs.php
8.4 缓存与编译
- CacheHelper::set() 用 array('__v'=>$value) 哨兵格式
- cache_truncate 用 deleteByPrefix,禁止 flushdb
- 修改 _include() 加载的文件后清 tmp/ 编译缓存
- PHP 8+ 不依赖 @ 抑制 Error,用 function_exists 守卫
8.5 前端
- 禁用 jQuery、Alpine.js、idiomorph
- htmx 事件名用冒号格式
- 后端 htmx 请求返回纯 HTML 片段
- 表单提交回调禁止 jform.reset()
- 按钮用 px-* 控制宽度,禁止 w-100
- JS/CSS 修改后递增 conf.json.version
8.6 数据
- SQL 中禁止 SELECT display_name / avatar_url / status(bbs_user 无此字段)
- db_* 函数 $table 参数禁止包含表前缀
- checkbox 必须声明 value="1"
- 分页过滤条件下沉 SQL,计数器递减用辅助函数