功能开发中
云团 Developer Docs

开发文档

面向开源版独立部署的完整实施与自研应用开发指南,从环境安装、后台初始化、官方服务绑定,到 addons 应用模块、接口、权限、前端构建和发布打包。

后端框架 ThinkPHP 8
应用目录 addons/{name}
后台前端 TypeScript + Vue3
发布产物 仅发布 PHP + build 后前端

阅读说明

这份文档面向下载开源版并独立部署云团的开发者和运营者。你可以把云团理解为一个可二次开发的应用运营平台:主系统负责登录、权限、上传、应用管理、订单和应用市场;每个业务应用放在 addons 目录下独立维护。

商家自研应用与平台应用使用同一套模块规范,但来源不同。商家自研应用属于本地站点资产,不需要远程平台授权;平台应用需要从平台应用市场购买或由平台授权后同步。

建议先完整跑通安装、后台登录、上传配置和远程平台绑定,再开始开发自研应用。这样开发时可以直接复用主系统提供的认证、权限、上传和应用管理能力。

安装部署

1. 准备运行环境

  • PHP 建议使用 8.1 或更高版本,并开启 fileinfoopensslpdo_mysqlmbstringcurlzip 扩展。
  • 开源版压缩包不携带 vendor,首次访问安装器前需要先在项目根目录执行 composer install --no-dev --optimize-autoloader
  • 数据库使用 MySQL 8 或兼容版本,字符集建议统一为 utf8mb4
  • Web 服务可使用 Nginx,站点运行目录需要指向项目的 public 目录。
  • 服务器需要允许 PHP 读写 runtimepublic/uploadsaddons 以及应用发布目录。

2. 安装 Composer 依赖

解压开源版后先进入项目根目录安装 PHP 依赖。安装完成并确认 vendor/autoload.php 存在后,再配置站点和打开安装器。

composer install --no-dev --optimize-autoloader

3. 推荐使用 Web 安装器

开源版交付包会在 public/install.php 提供首次安装入口,并把可二次开发的商家后台、自定义首页应用前端源码放在 public/source 下。首次部署时先把站点运行目录指向项目的 public 目录,再访问 /install.php,按页面提示填写数据库、当前域名、站点名称和后台管理员账号。

自动完成

安装器会创建数据库、导入 database/install.sql、写入开源版 .env、创建后台超级管理员,并生成本地默认运营主体。

安全收尾

安装成功后,public/install.php 会自动删除。若服务器权限导致删除失败,应手动确认该文件不再对外开放。

开源版的官方服务地址固定为 https://x.bot100.cn,安装器不会提供平台地址输入框;安装完成后进入 /admin,在商家绑定页面完成官方账号绑定。业务平台则在平台管理页面按需创建,购买或授权应用不会自动创建平台。

4. 保留手动或命令行安装

如果服务器不能使用 Web 安装器,也可以沿用手动导入方式。先创建数据库,再导入安装脚本。数据库名可以按实际环境调整,默认示例为 yuntuan

CREATE DATABASE `yuntuan` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

导入项目中的安装脚本:

mysql -uroot -p yuntuan < database/install.sql

完整源码或开发环境仍可使用命令行安装器:

php tools/install.php --install --db-host=127.0.0.1 --db-name=yuntuan --db-user=root --db-pass=your_password

5. 配置环境变量

手动安装时在项目根目录维护 .env。独立部署开源版至少需要配置数据库、发行版本、授权密钥和固定官方服务地址。

APP_DEBUG = false
DB_TYPE = mysql
DB_HOST = 127.0.0.1
DB_NAME = yuntuan
DB_USER = root
DB_PASS = your_password
DB_PORT = 3306
DB_CHARSET = utf8mb4
DB_PREFIX = yt_

YUNTUAN_EDITION = merchant
YUNTUAN_PLATFORM_API_BASE = https://x.bot100.cn
YUNTUAN_LICENSE_PATH = license.json
YUNTUAN_AUTH_KEY = 请生成并妥善保存的对称加密密钥
开源版 .env 只保留官方服务接口和本地运行配置,不应包含平台后台运行配置或平台私钥配置;平台后台源码、平台后台前端构建产物、vendornode_modules 都不应进入开源版交付包。

6. 配置 Nginx

Nginx 站点根目录应指向 public,并把不存在的文件转交给 index.php,这样 MVC 页面、API 和 Vue 后台路由都能正常工作。

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

平台绑定与授权

商家独立部署后,第一次进入后台应先绑定远程平台账号。绑定成功后,本地会保存商家编号、商家账号、授权域名、源码授权状态和已授权平台应用列表。

源码授权

源码销售按域名和商家账号共同绑定。后台登录时会检查上次授权校验时间,超过 24 小时会重新请求平台校验。

应用授权

平台应用需要远程平台授权后才能同步和安装。商家自研应用不走平台授权,默认内置应用由源码随附。

不要通过直接修改本地数据库的 authorized_addons 来尝试开通平台应用。商家版最终会以签名授权文件和远程平台校验结果为准,平台应用安装和升级也会再次校验授权。

商家后台

商家后台访问路径为 /admin。后台前端使用 TypeScript、Vite、Vue3、TailwindCSS 和 Ant Design Vue 开发,构建后的文件部署在 public/admin,接口统一走 /api

常用后台模块

  • 平台概览:查看系统信息、应用类型统计和运行状态。
  • 应用管理:管理已安装应用、从官方平台同步应用、安装自研应用包、进入应用独立后台。
  • 后台用户及权限:维护管理员账号、用户分组、后台菜单和分组权限。
  • 系统设置:维护上传存储方式,本地、七牛云、阿里云、腾讯云等配置都从这里管理。
  • 个人中心:维护当前账号昵称、头像和密码。

应用目录结构

每个应用都是一个独立模块,必须放在 addons 目录下。目录名就是应用标识,只允许小写字母、数字和下划线,并建议以业务名称命名。

addons/
└── demo_app/
    ├── manifest.json
    ├── route.php
    ├── install.sql
    ├── upgrade.sql
    ├── backend/
    │   ├── controller/
    │   │   └── api/
    │   ├── model/
    │   └── service/
    ├── frontend/
    │   ├── src/
    │   ├── package.json
    │   └── vite.config.ts
    └── public/
        └── admin/

目录职责

目录或文件 用途
manifest.json 应用清单,声明名称、版本、价格、支持平台、权限、菜单、接口前缀和打包规则。
route.php 应用独立路由,主系统启动时会自动加载 addons 下各应用的路由文件。
backend 应用 PHP 后端源码,包含控制器、模型、服务类等。
frontend 应用后台前端开发源码,仅开发阶段保留,发布应用包时不允许包含未构建源码。
public 应用构建后的前端和静态资源,安装时会发布到主站 public 下。

Manifest 清单

manifest.json 是应用被平台识别、安装和管理的核心文件。应用管理、安装器、远程同步都会读取它。

{
  "name": "demo_app",
  "title": "演示应用",
  "version": "1.0.0",
  "description": "用于演示商家自研应用开发规范。",
  "type": "system_plugin",
  "type_label": "系统插件",
  "source": "merchant_local",
  "market_visible": true,
  "merchant_market_visible": true,
  "default_installed": false,
  "icon": "/static/addons/demo_app/icon.svg",
  "price": 0,
  "support_platforms": ["merchant"],
  "backend_url": "/addons/demo_app/admin/",
  "api_prefix": "/api/addons/demo_app/admin",
  "permissions": [
    { "permission": "demo_app.manage", "title": "管理演示应用" }
  ],
  "menus": [
    {
      "title": "演示应用",
      "path": "/addons/demo_app/admin/",
      "permission": "demo_app.manage",
      "icon": "SettingOutlined",
      "sort": 10,
      "visible": true
    }
  ],
  "install": { "sql": "install.sql" },
  "upgrade": { "sql": "upgrade.sql" },
  "runtime": {
    "backend": true,
    "frontend": true,
    "php": ">=8.1",
    "yuntuan": ">=0.1.0",
    "frontend_source": "frontend"
  },
  "package": {
    "source": "frontend",
    "public": "public",
    "exclude": ["frontend/src", "frontend/node_modules", "frontend/**/*.map"],
    "include": ["manifest.json", "route.php", "install.sql", "upgrade.sql", "backend", "public"]
  }
}

关键字段说明

  • source:商家自研应用使用 merchant_local,平台应用统一使用 platform。远程同步只是安装渠道,不改变应用来源。
  • market_visible:是否在平台应用市场展示。
  • merchant_market_visible:安装到商家站点后,是否进入商家面向普通用户的本地应用市场。
  • default_installed:是否随源码默认安装,例如自定义首页这类站点级系统插件。
  • support_platforms:按应用实际兼容范围填写 platformmerchant,同时支持平台自营版和商家独立部署版时填写两项。
system_plugin 外,其他类型应用的后台和前台都必须绑定 app_idmerch_idplatform_id 三个参数。缺少任意一个参数时,入口和接口都应拒绝访问。

后端接口开发

应用后端建议按照控制器、服务类、模型分层。控制器只做认证、权限、参数接收和响应;业务逻辑写在服务类;数据库表操作写在模型或服务中。

路由示例

<?php

use think\facade\Route;

Route::get(
    'api/addons/demo_app/admin/setting',
    '\\addons\\demo_app\\backend\\controller\\api\\Setting@detail'
);

Route::put(
    'api/addons/demo_app/admin/setting',
    '\\addons\\demo_app\\backend\\controller\\api\\Setting@save'
);

控制器建议

  • 应用接口可以继承主系统已有的 API 基础控制器,以复用 token 认证、权限校验和统一响应。
  • 每个方法进入业务前先校验权限,例如 demo_app.manage
  • 接口只返回前端需要的字段,不要直接暴露数据库整行敏感字段。
  • 异常提示统一使用中文,方便商家后台直接展示。

数据库表命名

应用表建议使用 yt_应用标识_业务名,例如 yt_demo_app_setting。这样卸载、排查和迁移时都能快速识别归属。

运行上下文与数据隔离

system_plugin 是站点级系统插件,可以按站点或系统配置隔离;其他应用类型必须以 merch_id + platform_id + app_id 作为基础运行空间。

  • merch_id:商家编号,用于隔离不同商家。
  • platform_id:接入平台编号,用于隔离同一商家的不同平台。
  • app_id:应用编号,用于读取应用安装信息和授权关系。
CREATE TABLE IF NOT EXISTS `yt_demo_app_setting` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '配置编号',
  `merch_id` int unsigned NOT NULL DEFAULT 0 COMMENT '商家编号',
  `platform_id` int unsigned NOT NULL DEFAULT 0 COMMENT '接入平台编号',
  `app_id` int unsigned NOT NULL DEFAULT 0 COMMENT '应用编号',
  `config_json` json DEFAULT NULL COMMENT '扩展配置',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_merch_platform_app` (`merch_id`, `platform_id`, `app_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='演示应用设置表';
$row = Db::name('demo_app_order')
    ->where('id', $id)
    ->where('merch_id', $scope['merch_id'])
    ->where('platform_id', $scope['platform_id'])
    ->where('app_id', $scope['app_id'])
    ->where('user_id', $userId)
    ->find();

应用后台前端

应用后台前端仍使用 TypeScript、Vite、Vue3、TailwindCSS 和 Ant Design Vue。开发源码放在应用自己的 frontend 目录下,构建产物输出到应用自己的 public/admin

Vite 配置重点

  • base 应设置为应用后台访问路径,例如 /addons/demo_app/admin/
  • outDir 应输出到 addons/demo_app/public/admin
  • 请求接口统一使用应用自己的 api_prefix,例如 /api/addons/demo_app/admin
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'node:path'

export default defineConfig({
  base: '/addons/demo_app/admin/',
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  build: {
    outDir: '../public/admin',
    emptyOutDir: true,
  },
})

构建命令

pnpm install
pnpm build
发布到应用商店或交付给商家安装时,不要包含 frontend/srcnode_modules、源码映射文件和 TypeScript 构建缓存。只发布 PHP 后端、manifest、SQL、路由和 build 后的前端产物。

上传与资源

主系统提供统一上传能力,应用可以调用公共上传组件和上传接口。数据库中建议保存资源编号,而不是保存完整 URL。

为什么保存资源编号

  • 本地存储和云存储都可以通过同一个资源编号生成最终访问地址。
  • 云存储更换域名后,只需要更新上传配置,不需要批量修改业务表。
  • 资源表可以记录文件类型、大小、驱动、对象路径和创建时间,便于后续清理。

支持的存储方式

本地上传 七牛云直传 阿里云 OSS 直传 腾讯云 COS 直传

图片、视频、音频和附件都应走统一上传入口。当前后台优先使用图片选择器,后续应用需要其他文件类型时,保持相同接口协议即可。

接口清单

接口 方法 用途
/api/upload/assets GET scene、关键词、分组和分页读取当前登录用户的资源列表。
/api/upload/local POST 本地上传,表单字段为 filescenegroup_id
/api/upload/direct-params POST 云存储前端直传前,向后端申请七牛云、阿里云 OSS 或腾讯云 COS 的直传参数。
/api/upload/complete-direct POST 云存储直传完成后回调主系统登记资源,业务表后续只保存返回的资源编号。

后端保存资源编号

<?php

namespace addons\demo_app\backend\controller\api;

use app\controller\api\BaseApi;
use app\service\UploadService;
use think\facade\Db;
use think\Response;

/**
 * 演示应用设置接口。
 */
class Setting extends BaseApi
{
    /**
     * 保存应用封面资源编号。
     *
     * @return Response
     */
    public function save(): Response
    {
        $admin = $this->requirePermission('demo_app.manage');
        $scope = [
            'merch_id' => (int) ($admin['merchant_id'] ?? 0),
            'platform_id' => (int) $this->request->param('platform_id', 0),
            'app_id' => (int) $this->request->param('app_id', 0),
        ];
        $coverAssetId = max(0, (int) $this->request->param('cover_asset_id', 0));

        Db::name('demo_app_setting')->where($scope)->update([
            'cover_asset_id' => $coverAssetId,
            'update_time' => date('Y-m-d H:i:s'),
        ]);

        return $this->success(['cover_asset_id' => $coverAssetId], '保存成功');
    }

    /**
     * 读取设置并把资源编号转换为当前可访问地址。
     *
     * @param UploadService $uploadService 上传资源服务。
     * @return Response
     */
    public function detail(UploadService $uploadService): Response
    {
        $admin = $this->requirePermission('demo_app.manage');
        $scope = [
            'merch_id' => (int) ($admin['merchant_id'] ?? 0),
            'platform_id' => (int) $this->request->param('platform_id', 0),
            'app_id' => (int) $this->request->param('app_id', 0),
        ];
        $row = Db::name('demo_app_setting')->where($scope)->find() ?: [];
        $coverAssetId = (int) ($row['cover_asset_id'] ?? 0);

        return $this->success([
            'cover_asset_id' => $coverAssetId,
            'cover_url' => $uploadService->assetUrlById($coverAssetId),
        ]);
    }
}

前端公共组件示例

应用后台接入主系统公共组件后,可以直接使用资源选择器。组件返回资源编号,业务表只保存这个编号。

<script setup lang="ts">
import { reactive, ref } from 'vue'
import AssetPicker from '@/components/AssetPicker.vue'
import type { UploadAsset } from '@/services/upload'

const form = reactive({
  cover_asset_id: 0,
})

const coverUrl = ref('')

/**
 * 记录选择后的图片预览地址。
 *
 * @param asset 上传资源。
 */
function handleCoverSelected(asset: UploadAsset) {
  coverUrl.value = asset.url
}
</script>

<template>
  <AssetPicker
    v-model:value="form.cover_asset_id"
    scene="image"
    button-text="选择图片"
    @selected="handleCoverSelected"
  />
  <img v-if="coverUrl" :src="coverUrl" alt="封面预览">
</template>

前端直接调用接口示例

import { http } from '@/services/http'

/**
 * 上传图片并返回资源编号。
 *
 * @param file 浏览器选择的文件。
 */
export async function uploadCover(file: File) {
  const formData = new FormData()
  formData.append('scene', 'image')
  formData.append('group_id', '0')
  formData.append('file', file)

  const response = await http.post('/upload/local', formData)
  return response.data.data.id
}

统一支付

平台操作台的“系统设置”中可以维护微信支付和支付宝配置。应用后端不要自己分散设置回调地址,支付网关的统一回调入口由平台服务自动注入,应用只需要实现自己的业务回调服务;return_url 只用于支付成功后的同步跳转,不要把回调参数拼进去。

统一支付回调地址固定为 /api/payments/notify。微信和支付宝共用同一个入口,notify_url 不携带查询参数。平台在创建支付单时会把 out_trade_noapp_idmerch_idplatform_id 写入 platform_payment_context 表,回调时按订单号恢复上下文,再分发给应用自己的回调服务。

支持的支付场景

  • 微信:nativejsapimini_programh5app
  • 支付宝:pagewapappprecreatebarcode

统一返回契约

字段 含义
display_type 前端展示方式,常见值为 qrcodelinkhtmljson
code_url 二维码内容,适用于扫码类场景。
redirect_url 直接跳转链接,适用于 H5、网页支付等场景。
payment_html 支付表单或自动跳转 HTML,适用于支付宝网页支付。
json_data 无法直接渲染或需要补充参数时返回的原始响应,前端按 JSON 展示。

如果微信 jsapimini_program 缺少 openid,或者支付宝条码支付缺少 auth_code,接口会直接返回 display_type=json,前端按 JSON 结果展示和排查,不要把它当成错误页。

生产环境真实支付必须使用公网可访问的 HTTPS 回调地址;本地 http:// 域名只能用于模拟回调或前端联调。

应用后端示例

应用后端发起支付时,providerscene 也要一起传入,scene 由应用根据当前客户端自行判断,不要自己拼接回调地址:

<?php

$provider = 'wechat';
$scene = $provider === 'wechat' ? 'native' : 'page'; // 应用根据当前客户端自行决定具体场景。
$payment = app(\app\service\PlatformPaymentService::class);
$order = [
    'subject' => '演示应用订单',
    'amount' => '19.90',
    'out_trade_no' => 'DEMO' . date('YmdHis'),
    'app_id' => $appId,
    'merch_id' => $merchId,
    'platform_id' => $platformId,
    'return_url' => request()->domain() . '/addons/demo_app/pay-result',
];

$result = $provider === 'wechat'
    ? $payment->createWechatOrder($platformId, 'demo_app', $scene, $order)
    : $payment->createAlipayOrder($platformId, 'demo_app', $scene, $order);

// 平台会自动补充统一回调地址,并按 out_trade_no 记录应用上下文。

前端处理示例

前端不要直接拿支付密钥和证书,只调用应用自己的后端接口。provider 表示支付渠道,scene 表示支付场景,应用应该根据当前客户端自行判断场景,例如 Web 端微信常用 native,支付宝常用 page;移动浏览器可选择 h5wap。前端先判断 display_type,再分别处理二维码、跳转链接、HTML 表单和 JSON:

import { http } from '@/services/http'

export async function createPayment(orderId: number, provider: 'wechat' | 'alipay', scene: string) {
  const response = await http.post('/api/addons/demo_app/payment/pay', {
    order_id: orderId,
    provider,
    scene,
    return_url: window.location.href,
  })

  const payment = response.data.data

  if (payment.display_type === 'qrcode') return payment.code_url
  if (payment.display_type === 'link') return payment.redirect_url
  if (payment.display_type === 'html') return payment.payment_html
  return payment.json_data ?? payment
}

统一短信

短信使用当前平台配置中的服务商发送。阿里云、腾讯云和七牛云的参数并不相同,主系统服务会按服务商分别调用官方 SDK。

<?php

$sms = app(\app\service\PlatformSmsService::class);
$result = $sms->sendCaptcha($platformId, 'demo_app', '13800138000', [
    'code' => '123456',
], ['code']);

// 腾讯云短信会按最后一个参数中的变量顺序生成 TemplateParamSet。
服务商 后台配置项 发送差异
阿里云短信 AccessKeyIdAccessKeySecret、短信签名、模板 ID。 模板变量以 JSON 对象传入 TemplateParam,变量名必须与阿里云模板一致。
腾讯云短信 SdkAppIDAccessKeyIdAccessKeySecret、短信签名、模板 ID。 模板变量必须按模板变量顺序传入 TemplateParamSet,所以应用调用时建议显式传入变量顺序。
七牛云短信 AccessKeySecretKey、短信签名、模板 ID。 七牛发送接口使用 sendMessage(template_id, mobiles, parameters),签名通常在模板侧绑定。

后台一键测试

  • 统一支付:点击一键测试会先自动保存当前配置;微信支付支持扫码支付、公众号支付、小程序支付、H5 支付和 APP 支付;支付宝支持电脑网站支付、手机网站支付、APP 支付、扫码支付和条码支付。能展示二维码或跳转页的结果会直接展示,不能直接生成链接的结果会返回 JSON。
  • 统一短信:短信测试会从模板内容自动识别 ${code}{{code}}{code} 这类变量,让管理员填写后发送。

权限与菜单

应用后台是独立入口,但仍建议使用主系统的后台账号和权限体系。应用权限写在 manifest 的 permissions 中,应用菜单写在 menus 中。

  • 权限标识建议使用 应用标识.动作,例如 demo_app.manage
  • 菜单路径建议指向应用独立后台,例如 /addons/demo_app/admin/
  • 平台后台和商家后台的主菜单不应直接塞入应用内部页面,应用后台应该在新标签或独立入口打开。
  • 如果应用需要多级权限,可以在应用内部继续维护自己的功能菜单,但接口权限仍应回到主系统统一校验。

Manifest 权限菜单示例

{
  "permissions": [
    { "permission": "demo_app.manage", "title": "管理演示应用" },
    { "permission": "demo_app.order.view", "title": "查看演示应用订单" }
  ],
  "menus": [
    {
      "title": "演示应用",
      "path": "/addons/demo_app/admin/",
      "permission": "demo_app.manage",
      "icon": "AppstoreOutlined",
      "sort": 20,
      "visible": true
    }
  ]
}

接口权限校验示例

<?php

/**
 * 查询订单列表。
 *
 * @return \think\Response
 */
public function orders(): \think\Response
{
    $admin = $this->requirePermission('demo_app.order.view');

    return $this->success([
        'items' => [],
        'operator' => $admin['username'] ?? '',
    ]);
}

打包发布

商家自研应用可以在本地打包后通过商家后台“安装应用”上传;如果希望把自研应用提交到云团应用市场销售或分发,则继续使用商家后台的“开发者中心”提交审核。

发布前检查清单

  • 确认 manifest.json 中的 name 与应用目录名完全一致。
  • 确认 backend_urlapi_prefix 都绑定当前应用目录。
  • 执行前端 pnpm build,确保 public/admin 存在入口文件。
  • 确认安装 SQL 可重复执行,不要破坏已有数据。
  • 确认压缩包不包含未构建前端源码、node_modules、调试日志和本地密钥。

推荐压缩包内容

demo_app.zip
├── manifest.json
├── route.php
├── install.sql
├── upgrade.sql
├── backend/
└── public/

提交应用市场

商家自研应用可以只在自己的站点安装,也可以提交到云团应用市场。提交入口位于商家后台“开发者中心”,平台审核通过后会生成独立的平台发行包;开发者上传的原始包不会直接作为市场安装包发布。

独立部署站点必须先绑定云团官方账号。较早版本完成的绑定没有开发者访问凭证,需要在商家绑定页面重新绑定一次,再进入开发者中心。

1. 完善开发者资料

首次进入开发者中心,先填写开发者或团队名称、联系人、电话、邮箱、网站和简介。开发者名称会随应用资料提交审核,并可用于应用市场展示。

2. 创建应用提交

  • 填写应用名称、应用标识、类型、描述、建议售价和发布范围。
  • 应用标识只能使用小写字母、数字和下划线,并且以小写字母开头。
  • 应用标识在创建申请时即全局保留,其他开发者不能再提交同名应用。
  • 没有应用前台的系统工具应关闭“包含应用前台”,避免市场展示无效前台入口。

3. 准备并上传安装包

提交包必须是可直接安装的 ZIP 运行包,manifest.jsonname 必须与申请标识一致,source 必须为 merchant_local。包内可以包含 PHP 后端、SQL 和 build 后的前端,但不能包含未构建的 Vue/TypeScript 源码、node_modules、source map、环境文件、证书、密钥、日志或备份。

上传后系统会自动检查 ZIP 路径安全、符号链接、文件数量和大小、敏感文件、Manifest、一致性以及 SQL 表作用域。应用 SQL 只能操作 __PREFIX__{addon_name}_* 私有表。

4. 提交审核

  1. 确认自动校验全部通过,并填写本次版本更新说明。
  2. 点击“提交审核”。审核期间应用资料和版本包不能修改。
  3. 审核通过后,平台会重新生成 source=platform 的发行包,并按审核结果上架或保留为待上架。
  4. 审核驳回后,在详情中查看原因,修改资料或上传新版本,再次提交审核。
状态 含义 开发者可执行操作
草稿资料或安装包尚未提交审核。编辑资料、上传版本、提交审核。
待审核平台正在审核当前版本。查看详情和审核进度。
已驳回当前版本存在需要修正的问题。查看驳回原因、修改后重新提交。
已通过版本审核通过,但应用尚未公开上架。维护资料或上传后续版本。
已上架当前审核版本已经进入应用市场。维护资料、上传新版本并发起新一轮审核。

5. 发布后更新

应用上架后仍使用同一条应用提交记录。先编辑需要调整的资料,再上传更高版本号的 ZIP;上传成功后状态会回到草稿,提交审核并通过后,新版本成为平台当前发行版本,历史版本和审核记录继续保留。

不要把商家自研包的 source 手动改成 platform。平台来源只能由审核发布流程生成,令牌、平台密钥和审核状态也不能写入应用安装包。

应用市场规则

云团同时支持平台自营应用市场和商家独立部署后的本地应用市场。两者展示规则不同,需要在开发应用时提前确认。

场景 展示规则 适用应用
平台应用市场 应用已安装、启用、上架、审核通过,并且 market_visible=1 平台上架售卖或免费分发的应用。
商家本地应用市场 商家自研应用或已授权平台应用,并且 merchant_market_visible=1 商家面向普通用户售卖或开放使用的应用。
默认内置应用 随源码默认安装,可在商家后台应用管理中使用,但可以不进入商家本地应用市场。 自定义首页、站点配置、系统工具等站点级应用。

展示规则配置示例

{
  "source": "merchant_local",
  "market_visible": true,
  "merchant_market_visible": true,
  "default_installed": false,
  "has_frontend": true,
  "backend_url": "/addons/demo_app/admin/",
  "frontend_url": "/app"
}

如果应用没有前台入口,例如站点级工具或自定义首页后台,请把 has_frontend 配置为 false,这样系统不会展示前台按钮,也不会要求绑定前台域名。

安全建议

  • 生产环境关闭 APP_DEBUG,避免暴露错误堆栈和调试入口。
  • YUNTUAN_AUTH_KEY、数据库密码、云存储密钥等只放在服务器环境配置中,不要提交到应用包。
  • 应用接口必须校验登录态和权限,不能只依赖前端菜单隐藏。
  • 上传文件必须限制类型、大小和存储目录,前端直传完成后仍要回调服务端登记资源。
  • 商家版不要通过本地数据库直接伪造平台应用授权,安装、同步和升级流程都应依赖远程授权。

常见问题

刷新后台页面 404

确认 Nginx 已配置 try_files $uri $uri/ /index.php?$query_string;,并且主系统路由中存在后台前端兜底入口。

接口返回 404

确认应用的 route.php 已放在应用目录下,并且路由前缀与 manifest.json 中的 api_prefix 一致。

应用后台空白

确认应用前端已经 build,入口文件和静态资源在 public/admin 内,且 Vite 的 base 与访问路径一致。

上传后的图片换域名失效

业务表应保存上传资源编号,由接口根据当前上传配置生成完整 URL。如果保存了旧域名完整路径,换域名后就需要手动迁移历史数据。