onezan-admin 文档中心 项目整体架构、目录结构、开发规范、安装方式与交付要求的统一入口
项目开发文档 接口中心 图标文档 后台
文档中心 总体说明 项目概览

onezan-admin 快速开发框架

壹站 onezan-admin 适用于核心接入应用实现多样化可定制、快速交付的场景。项目采用前后端分离结构,内置系统核心功能:用户、渠道多端、权限控制和基础配置能力,可作为新项目的统一底座继续扩展。

开发文档
docs /docs/
服务端:ThinkPHP 8 后台端:Vue 3 + TypeScript + TDesign 统一入口:/docs /apidoc /admin
整体架构

后端采用框架核心 + 框架基础能力 + 应用 + 模块 / 插件结构,前端按框架基础能力、模块、插件三类后台页面组织。

内置应用

框架内置 sample 示例应用(快速开发模板)应用独立拥有 manifest、event.php、菜单、权限、消息、设置,互不耦合。框架核心不包含任何应用专属逻辑。

技术栈

项目遵循前后端分离架构,服务端接管业务逻辑与数据持久化,管理后台负责页面渲染与交互,两端通过 RESTful API 通信。以下版本号为系统最低要求,生产环境建议采用更新版本以保证安全与性能。

服务端

语言:PHP 最低 ≥ 8.0,建议 ≥ 8.2(严格模式,声明类型)
框架:ThinkPHP 8.0(PSR-4)
ORM:ThinkORM 3.x / 4.x
认证:firebase/php-jwt Token 鉴权
队列:sync / database / redis 三驱动 + Supervisor 常驻
支付:yansongda/pay 3.x(微信 + 支付宝)
消息:w7corp/easywechat(公众号 / 小程序)、phpmailer
存储:think-filesystem(本地 + 云存储扩展)
运行环境:Nginx / Apache + PHP-FPM
数据库:MySQL 最低 ≥ 5.7,建议 ≥ 8.0
缓存:Redis 最低 ≥ 5.0,建议 ≥ 7.0

管理后台

语言:TypeScript 5.x 严格模式
框架:Vue 3.5 Composition API
构建:Vite 6.x
UI 库:TDesign Vue Next 1.18
状态:Pinia 3.x + pinia-plugin-persistedstate
路由:Vue Router 4.x
HTTP:Axios 1.x(请求/响应拦截)
图表:ECharts 5.4
样式:Less 4.x + TDesign 设计变量
质量:ESLint 9 + Stylelint 16 + vue-tsc 类型检查
Node:最低 ≥ 20.19,建议 ≥ 20.x LTS / 22.x LTS

用户前端

参考前端:暂无

分层 后端:路由(route)→ 控制器(controller,参数校验 + 业务校验)→ 应用服务(service,参数收口 + 幂等)→ 核心服务(core,写流程 + 事务 + 状态流转)→ 仓储(repository,数据访问封装)→ 模型(model,映射数据表)。前端:页面(pages)→ 组件(components)→ API 层(api)→ 状态(store)→ 路由(router)。

目录结构

下面展示的是项目完整目录结构,所有开发都应围绕这套结构展开。

术语 本文档中的“核心框架”只指 api/core;“框架基础能力”只指 api/app 中通用控制器、服务、仓储、模型、校验;“应用”只指 api/applications/{app};“模块”只指 modules/{module};“插件”指可关闭、可替换、可移除的扩展单元。

层级 目录 职责
核心框架 api/core 注册中心、聚合器、安装器、菜单在线覆盖、队列核心等能力。
框架基础能力 api/app/adminapi/controller、api/app/api/controller、api/app/service、api/app/repository、api/app/model、api/app/validate 框架公共接口、服务、仓储、模型、校验与中间件。
应用 api/applications/{app} 应用清单 manifest、事件注册 event.php、菜单、权限、消息、设置、桥接路由、应用专属控制器/事件/监听器。
模块 api/applications/{app}/modules/{module} 某一模块的 service、core、repository、model、validate、manifest、route、queue.php。
插件 api/applications/{app}/plugins/{plugin} 应用内可插拔扩展能力,如优惠券、会员、充值等。
框架基础页面 admin/src/core/pages、admin/src/core/api 框架内置了基础页面:如管理员、用户、渠道、系统配置、权限角色、菜单配置。
功能模块页面 admin/src/applications/{app}/pages、admin/src/applications/{app}/api 模块页面与接口,如商品、内容、订单、财务。
扩展插件页面 admin/src/applications/{app}/plugins/{plugin}/pages、admin/src/applications/{app}/plugins/{plugin}/api 插件页面与接口,如充值、签到。
路由层 admin/src/router/core/entry.ts、admin/src/router/registry.ts、admin/src/applications/{app}/router/*.ts、admin/src/applications/{app}/plugins/router.ts 框架路由组聚合;应用路由文件自动发现;插件路由由各应用自己的 plugins/router.ts 自动收集。

前端规范

前端的首要目标是让新增页面与框架内置页面保持同一套 UI、布局、按钮、弹窗、提示和交互风格。

页面放置

框架基础能力页面放 admin/src/core/pages;模块页面放 admin/src/applications/{app}/pages/{biz}(如 applications/mall/pages/goods);插件页面放 admin/src/applications/{app}/plugins/{plugin}/pages。

API 放置

框架基础能力接口放 admin/src/core/api;模块接口放 admin/src/applications/{app}/api;插件接口放 admin/src/applications/{app}/plugins/{plugin}/api。

列表页风格

强制 DOM 结构:t-card(:bordered="false") → t-form.search-form → t-table → t-dialog。禁止冗余包裹和 t-card title。

按钮顺序

查询 → 重置 → 主操作。查询 theme="primary",重置 theme="default" variant="base"。

标签与状态

状态、类型、来源等枚举统一使用 t-tag,轻量边框或浅色填充,不额外造新的视觉体系。

图标规则

菜单图标用本地 iconfont icon-menu-*;二级菜单不显示图标;列表页操作按钮统一纯文字无图标;卡片展示区仍可用 AppIcon 做装饰。

框架共享组件

admin/src/components/ 提供框架级通用组件:AppIcon、UserTwoLine、RegionPicker(省市区三级地区选择,基于 element-china-area-data + t-cascader,应用可直接复用)。

命名惯例

用途 建议命名 说明
加载状态 dataLoading 所有页面统一。
查询表单 searchForm 用 reactive 包裹。
列表数据 tableData 单文件内保持一致。
列表请求 fetchList() 统一入口。
查询/重置 handleSearch() / handleReset() 查询重置到第一页。
分页 pagination { current, pageSize, total }。
保存 handleSave() 配合 saving 状态。
弹窗 dialogVisible 配合 openAdd() / openEdit(row)。

页面范式

1. 简单 CRUD:列表页 + 弹窗
2. 字段较多:列表页 + edit.vue 独立编辑页
3. 在线配置:顶部说明 + 可编辑表格 + 统一保存按钮
4. 详情展示:抽屉或描述列表,不额外引入陌生交互

应用设置页

应用可通过 menus.json 将设置页菜单自动注入到"系统设置"二级菜单下,无需修改核心框架代码。

步骤文件说明
1api/applications/{app}/menus.json新增菜单项,parent_code 设为 "system"(自动挂载到系统设置下)
2admin/src/applications/{app}/router/settings.ts创建路由文件,用 Layout 包裹,path 必须以 / 开头。文件存在即生效,无需单独 entry
3admin/src/applications/{app}/pages/settings/index.vue实现设置页组件,建议用 t-tabs 分 Tab 组织配置区
菜单注入规则 设置页 parent_code 固定为 "system",path 必须以 / 开头(如 "/mall/settings")。系统会自动将其作为"系统设置"的二级菜单展示,无需修改 Core/manifest/menus.json。

路由模板

// admin/src/applications/{app}/router/settings.ts
import type { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';

export default [
  {
    path: '/{app}/settings',
    name: 'AppSettings',
    component: Layout,
    redirect: '/{app}/settings/index',
    meta: {
      title: { zh_CN: '应用设置', en_US: 'App Settings' },
      icon: 'iconfont iconshezhi1',
      orderNo: 50,
      permission: 'system.view',
    },
    children: [
      {
        path: 'index',
        name: 'AppSettingsIndex',
        component: () => import('@/applications/{app}/pages/settings/index.vue'),
        meta: {
          title: { zh_CN: '应用设置', en_US: 'App Settings' },
          permission: 'system.view',
        },
      },
    ],
  },
] satisfies RouteRecordRaw[];

菜单层级约束

每个 parent_code 为空的主菜单项(一级菜单)必须至少有一个 parent_code 指向它的子菜单项。如果缺少子项,侧边栏会出现冗余的二级点击层级(左侧点击主菜单后右侧仅显示主菜单自身)。框架已提供运行时兜底:若服务端菜单树中某父节点无子项,前端会从路由中自动解析子菜单。

规则 新增一级菜单时,必须同步新增至少一个 parent_code 指向该菜单的子菜单项。订单、财务等自带多个子路由的模块只需写 parent_code 子项清单。不可只写 parent 不写 child。

正确示例

// api/applications/mall/menus.json — 订单管理(一级菜单 + 子菜单)
[
  {
    "code": "order",
    "title": "订单管理",
    "title_short": "订单",
    "path": "/order/index",
    "icon": "iconfont icon-menu-order",
    "parent_code": "",
    "order_no": 50
  },
  {
    "code": "order.index",
    "title": "订单列表",
    "path": "/order/index",
    "icon": "iconfont iconliebiao",
    "parent_code": "order",
    "order_no": 10
  }
]

财务示例(多子菜单)

// api/applications/mall/menus.json — 财务(一级菜单 + 三个子菜单)
[
  {
    "code": "finance",
    "title": "财务管理",
    "title_short": "财务",
    "path": "/finance/payments",
    "icon": "iconfont icon-menu-finance",
    "parent_code": "",
    "order_no": 60
  },
  {
    "code": "finance.money-detail",
    "title": "余额明细",
    "path": "/finance/money-detail",
    "parent_code": "finance",
    "order_no": 10
  },
  {
    "code": "finance.payments",
    "title": "支付记录",
    "path": "/finance/payments",
    "parent_code": "finance",
    "order_no": 20
  },
  {
    "code": "finance.points",
    "title": "积分明细",
    "path": "/finance/points-detail",
    "parent_code": "finance",
    "order_no": 30
  }
]

路由自动发现

应用模块路由和插件路由均通过 import.meta.glob 零配置自动发现,新增应用或模块无需修改核心框架代码。

// admin/src/router/registry.ts — 固定路由自动发现
const applicationModules = import.meta.glob(
  ['../applications/**/router/!(entry).ts', '../applications/**/plugins/router.ts'],
  { eager: true },
);
// 应用路由文件放 admin/src/applications/{app}/router/{module}.ts
// 插件聚合入口放 admin/src/applications/{app}/plugins/router.ts
// 文件存在即生效,无需手动 import

// admin/src/utils/route/index.ts — 全局页面自动扫描
const dynamicViewsModules = {
  ...import.meta.glob('../../core/pages/**/*.vue'),
  ...import.meta.glob('../../applications/*/pages/**/*.vue'),
  ...import.meta.glob('../../applications/*/plugins/pages/**/*.vue'),
};
零配置约定 应用路由文件放到 admin/src/applications/{app}/router/{module}.ts,插件聚合入口放到 admin/src/applications/{app}/plugins/router.ts。框架自动 glob 加载,无需任何手动注册。页面自动扫描使用通配符 applications/*/pages/** 与 applications/*/plugins/pages/**。

后端规范

后端开发围绕框架核心放 api/core,框架基础能力放 api/app,应用、模块与插件放 api/applications。

固定分层

Controller 应用 Service Core Service Repository Model DB。复杂查询下沉 Repository,复杂写入下沉 Core Service。

写操作校验

写操作必须执行格式校验 + 领域校验;读操作至少做格式校验。不要在控制器和 Service 里堆零散 if。

路由组织

框架基础能力路由走 api/route/admin/*.php、api/route/app/*.php;模块与插件使用各自目录下的 route/admin.php、route/app.php。

更新缓存

菜单、权限、消息、设置、manifest 改动后,通过 POST /adminapi/sys/platform/sync 或后台更新缓存刷新聚合结果,并清理 api/runtime/cache、api/runtime/log 运行时文件。

校验结构

层 目录 职责
格式校验 api/app/validate/admin、api/app/validate/api 以及模块 / 插件自身 validate/admin、validate/api 字段类型、必填、长度、枚举等。
领域校验 api/app/validate/domain 或模块 / 插件自身 validate/domain 跨字段业务规则、场景规则。
自定义规则 api/app/validate/rule 手机号、证件号、小数等可复用规则。

应用开发

应用是一组完整业务能力的集合(如 mall)。框架设计上,框架核心与框架基础能力不变,每个应用独立部署,互不污染。

应用目录结构

api/applications/{app}/
├─ manifest.json          ← 应用清单(名称、版本、依赖、路由入口)
├─ menus.json             ← 后台左侧菜单树
├─ permissions.json       ← 权限节点注册
├─ messages.json          ← 消息模板注册
├─ settings.json          ← 应用级配置项
├─ event.php              ← 应用事件声明
├─ queue.php              ← 应用队列声明
├─ route/admin.php        ← 后台路由桥接入口
├─ route/app.php          ← 前台路由桥接入口
├─ modules/{module}/      ← 模块(goods / order / user 等)
├─ plugins/{plugin}/      ← 插件(vip / coupon / recharge 等)
│  ├─ extension/          ← 扩展点处理器(同步 Pipeline)
│  ├─ queue/              ← 队列 handler(异步)
│  ├─ event.php           ← 插件事件声明(可选)
│  └─ queue.php           ← 插件队列声明(可选)

快速开始:一行命令创建应用

推荐方式 使用 CLI 脚手架一键生成完整骨架,包含后端控制器/Service/Repository/Model/Validator + 前端页面/API/路由,省去手动创建目录和文件的步骤。
// === 创建应用(后端 + 前端 全部骨架) ===
php think make app myapp

// === 创建模块(含 CRUD 骨架代码) ===
php think make module myapp goods

// === 创建插件(含扩展点目录) ===
php think make plugin myapp vip

手动创建(了解原理)

// 1. 创建目录
mkdir -p api/applications/myapp
mkdir -p api/applications/myapp/modules
mkdir -p api/applications/myapp/plugins
mkdir -p api/applications/myapp/data

// 2. 创建 manifest.json
// api/applications/myapp/manifest.json
{
  "code": "myapp",
  "name": "我的应用",
  "version": "1.0.0",
  "description": "自定义应用",
  "dependencies": ["core"],
  "modules": [],
  "plugins": [],
  "route_bridges": {
    "admin": "applications/myapp/route/admin.php",
    "app": "applications/myapp/route/app.php"
  }
}

// 3. 创建路由桥接
// api/applications/myapp/route/admin.php
onezan_load_php_tree(array_merge(
    glob(dirname(__DIR__) . '/modules/*/route/admin.php') ?: [],
    glob(dirname(__DIR__) . '/plugins/*/route/admin.php') ?: []
));

// 4. 创建安装 SQL
// api/applications/myapp/data/schema.sql
CREATE TABLE IF NOT EXISTS `onezan_myapp_config` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `key` varchar(64) NOT NULL DEFAULT '',
  `value` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

运行机制

步骤操作说明
1创建应用目录与 manifest框架通过 manifest.json 识别应用
2注册路由桥接route/admin.php 和 route/app.php 是本应用的总入口
3添加模块或插件在 modules/ 或 plugins/ 下按规范创建
4同步菜单权限修改 menus.json 和 permissions.json
5在后台系统设置中点击"更新缓存",使新应用生效
📌php think make app {code}一键创建应用骨架(推荐,替代步骤 1-2)
📌php think make module {应用} {编码}一键创建模块骨架(推荐,替代步骤 3)
📌php think make plugin {应用} {编码}一键创建插件骨架(推荐,替代步骤 3)

插件开发

插件是可关闭、可替换、可移除的扩展单元。它通过事件订阅、扩展点管道和 Provider 接口与模块解耦协作。

框架扩展点清单

所有可用的扩展点定义在 api/core/manifest/extension_points.json,插件开发者可查看此文件了解可挂载的钩子:

扩展点说明
plugins.snapshot用户插件快照数据注入(VIP 状态、优惠券等)
order.price.calculate订单价格计算 — 插件可在支付前修改价格
order.after_create订单创建后触发 — 锁定优惠券、发放积分、发送通知
goods.price.calculate商品价格计算 — VIP 会员折扣
order.shipping.calculate运费计算 — VIP 免邮、地区定价

插件目录结构

api/applications/mall/plugins/my_plugin/
├─ manifest.json              ← 声明插件信息、扩展点、依赖
├─ event.php                  ← 插件事件声明(可选)
├─ queue.php                  ← 插件队列主题声明(可选)
├─ route/admin.php            ← 后台路由
├─ route/app.php              ← 前台路由
├─ controller/adminapi/       ← 后台控制器
├─ controller/api/            ← 前台控制器
├─ service/admin/             ← 后台 Service
├─ service/api/               ← 前台 Service
├─ core/                      ← 核心业务逻辑(Core Service)
├─ repository/                ← 数据查询层
├─ validate/                  ← 校验层
├─ extension/                 ← 扩展点处理器(实现 ExtensionPointHandlerInterface)
└─ queue/                     ← 队列 handler(异步消费)

快速开始:创建一个插件

// 1. 创建 manifest.json
// api/applications/mall/plugins/my_plugin/manifest.json
{
  "code": "mall.my_plugin",
  "name": "我的插件",
  "version": "1.0.0",
  "type": "plugin",
  "status": 1,
  "is_removable": true,
  "description": "演示插件",
  "files": [
    "api/applications/mall/plugins/my_plugin"
  ],
  "tables": [],
  "route_bridges": {
    "admin": "applications/mall/plugins/my_plugin/route/admin.php",
    "app": "applications/mall/plugins/my_plugin/route/app.php"
  },
  "permissions": ["plugins.my_plugin.view"],
  "dependencies": ["core", "mall"],
  "extension_points": [
    {
      "point": "order.price.calculate",
      "handler": "Applications\\mall\\plugins\\my_plugin\\extension\\MyPriceHandler",
      "priority": 30
    }
  ],
  "plugins_meta": {
    "type": "other",
    "description": "演示插件能力。",
    "icon": "iconfont iconplugin",
    "sort": 99
  }
}

// 2. 实现扩展点处理器
// api/applications/mall/plugins/my_plugin/extension/MyPriceHandler.php

use Core\contract\ExtensionPointHandlerInterface;

class MyPriceHandler implements ExtensionPointHandlerInterface
{
    public function point(): string
    {
        return 'order.price.calculate';
    }

    public function handle(array $ctx): array
    {
        // 读取管道上下文
        $userId = (int)($ctx['user_id'] ?? 0);
        $currentPrice = (float)($ctx['final_price'] ?? 0);

        // 业务判断
        if (!$this->isEligible($userId)) {
            return $ctx; // 不修改,原样传递
        }

        // 计算折扣(满100减10)
        $discount = $currentPrice >= 100.00 ? 10.00 : 0;
        $ctx['discount'] = ((float)($ctx['discount'] ?? 0)) + $discount;
        $ctx['final_price'] = round($currentPrice - $discount, 2);
        $ctx['breakdown'][] = [
            'type' => 'my_plugin',
            'label' => '满减优惠',
            'amount' => $discount,
        ];

        return $ctx; // 传递给下一个处理器
    }
}

// 3. 注册后台路由(扁平路由,与模块路由风格一致)
// api/applications/mall/plugins/my_plugin/route/admin.php
use think\facade\Route;
Route::get('mall/plugins/my_plugin/config', 'Applications\mall\plugins\my_plugin\controller\adminapi\Config@index');

插件与模块的解耦规则

通信方式方向机制适用场景
ExtensionPoint 模块 → 插件 同步管道,按优先级串联执行,收集返回值 价格计算、折扣叠加、运费计算
Event + Queue 模块 → 插件 异步投递,不阻塞主流程 下单后发券、支付后通知、数据上报
Provider 接口 插件 → 模块 插件通过接口调用模块的读写能力 插件查询用户等级、获取商品列表
API 直连 前端 → 插件后台 插件独立的后台 CRUD 接口 插件自身的配置、列表、管理功能
核心原则 模块不 import 插件代码。插件通过扩展点、事件、Provider 接口与模块协作,确保可插拔。

通信处理

框架提供三种通信机制:事件(Event)、队列(Queue)和扩展点(ExtensionPoint),分别对应异步投递、异步消费和同步管道三种模式。

三种机制对比

特性事件(Event)队列(Queue)扩展点(ExtensionPoint)
执行方式同步触发 Listener异步消费 Job同步 Pipeline
阻塞主流程是(Listener 内)否(独立进程)是(串联执行)
返回值不关心不关心收集并聚合
优先级无按入队顺序按 priority 排序
典型场景日志、缓存清理发券、通知、上报价格计算、数据聚合

事件投递

事件分两层注册:框架层在 core/manifest/event.php 注册通用事件(如用户注册),应用层在各 applications/{app}/event.php 注册应用专属事件(如订单创建)。模块写完数据库后抛出事件,Listener 接收后决定是同步处理还是投递队列。

分层规则 框架只注册通用事件(UserRegistered、UserVerified),订单、商品等应用事件由各 application 自行注册,框架层不包含任何应用专属事件。
// 1. 应用层定义事件类
// api/applications/mall/event/OrderCreated.php
namespace Applications\mall\event;
class OrderCreated
{
    public int $orderId;
    public array $orderData;
    public function __construct(int $orderId, array $orderData) {
        $this->orderId = $orderId;
        $this->orderData = $orderData;
    }
}

// 2. 应用 Core Service 中触发事件
// api/applications/mall/modules/order/core/CoreOrderLifecycleService.php
use Applications\mall\event\OrderCreated;

class CoreOrderLifecycleService
{
    public function create(array $payload): array
    {
        $order = $this->repository->create(/* ... */);

        // 抛事件(有已注册的 Listener 时自动触发)
        app()->event->trigger(new OrderCreated((int)$order->id, $order->toArray()));

        return ['code' => 0, 'data' => ['id' => (int)$order->id]];
    }
}

// 3. 应用 event.php 注册事件-监听关系
// api/applications/mall/event.php
return [
    'listen' => [
        \Applications\mall\event\OrderCreated::class => [
            \Applications\mall\listener\OrderCreatedListener::class,
        ],
    ],
];

队列消费

Listener 接收事件后通过 QueueDispatcher 投递队列,队列 handler 异步执行耗时逻辑。框架支持一个 topic 对应多个 handler(multi-cast),并通过 queue.php 声明式自动装载。

命名约定 队列 topic 加应用前缀避免冲突,如 mall.order.created.audit、legacy.order.created.audit。框架层仅提供通用 topic(user.registered.audit、upgrade.execute 等)。
// 1. 应用 Listener 接收事件后投递队列
// api/applications/mall/listener/OrderCreatedListener.php
namespace Applications\mall\listener;
use Applications\mall\event\OrderCreated;
use Core\service\queue\QueueDispatcher;

class OrderCreatedListener
{
    public function handle(OrderCreated $event): void
    {
        app()->make(QueueDispatcher::class)->dispatch(
            'mall.order.created.audit',
            ['order_id' => $event->orderId, 'data' => $event->orderData],
            ['biz_key' => 'mall.order.created:' . $event->orderId]
        );
    }
}

// 2. 应用层/模块层声明 queue.php(LifecycleDefinitionLoader 自动装载)
// api/applications/mall/modules/order/queue.php
use Applications\mall\modules\order\handler\OrderCreatedAuditHandler;

return [
    'mall.order.created.audit' => [
        OrderCreatedAuditHandler::class,
    ],
];

// 3. 队列 handler 实现
// api/applications/mall/modules/order/handler/OrderCreatedAuditHandler.php
namespace Applications\mall\modules\order\handler;
use app\Core\service\queue\contract\QueueHandlerInterface;

class OrderCreatedAuditHandler implements QueueHandlerInterface
{
    public function topic(): string
    {
        return 'mall.order.created.audit';
    }

    public function handle(array $payload): void
    {
        $orderId = (int)($payload['order_id'] ?? 0);
        // 异步审计 / 通知逻辑
    }
}

// 4. 插件也可以声明自己的 queue.php(多 Handler 消费同一 topic)
// api/applications/mall/plugins/coupon/queue.php
use Applications\mall\plugins\coupon\queue\CouponOrderCompletedHandler;

return [
    'mall.order.completed.followup' => [
        CouponOrderCompletedHandler::class,
    ],
];

扩展点管道

扩展点用于需要同步串联执行并收集返回值的场景。所有注册的处理器按 priority 从小到大依次执行,每个处理器的输出作为下一个的输入。

// 1. 模块调用扩展点
use app\Core\service\extension\ExtensionRegistry;

class CoreOrderService
{
    public function calculatePrice(int $userId, float $price, array $goodsItems): array
    {
        $ctx = [
            'user_id'       => $userId,
            'original_price' => $price,
            'final_price'   => $price,
            'discount'      => 0,
            'breakdown'     => [],
            'goods_items'   => $goodsItems,
        ];

        $registry = app()->make(ExtensionRegistry::class);

        // 管道计算:VIP(10) → Coupon(20) → Points(30)
        $result = $registry->execute('order.price.calculate', $ctx);

        return $result; // $result['final_price'] 已叠加所有折扣
    }
}

// 2. 插件实现处理器(见上方"插件开发"章节的完整示例)
选择建议 需要返回值且串联执行 → ExtensionPoint(如价格计算);
不关心返回值、允许延迟 → Event + Queue(如发券、通知);
不关心返回值、需同步完成 → Event + Listener(如日志记录)。

支付模块:Gateway Driver 模式

设计动机

支付模块需要对接多个第三方支付服务商(微信、支付宝、余额、线下转账),且不同应用的支付逻辑各有不同。为避免在每个应用中重复编写 SDK 对接代码,框架采用 Gateway Driver 模式 实现框架层复用。

核心收益:新增支付方式只需新增一个 Gateway 类并注册到编排器,无需修改任何已有代码(开闭原则)。

分层架构

层级组件职责目录
应用层MallPayBridge电商特有业务(余额扣减、订单完成、退款审核)applications/mall/modules/pay/core/
框架层CorePayOrchestrator支付编排(网关注册、分发、路由、可用选项查询)app/service/domain/pay/
CoreRefundOrchestrator退款编排(退款提交、退款回调路由)
网关层PaymentGatewayInterface统一契约(9个方法)app/service/domain/pay/gateway/
BaseGateway抽象基类(金额转换、测试 Mock)
WechatGateway微信支付 SDK 封装
AlipayGateway支付宝 SDK 封装
BalanceGateway余额支付(无需 SDK)
OfflineGateway线下转账(无需 SDK)

核心接口

interface PaymentGatewayInterface
{
    public function create(array $payment, array $order, string $terminal, array $context): array;
    public function notify(Request $request, string $terminal): array;
    public function refund(array $refund, array $payment): array;
    public function refundNotify(Request $request, string $terminal): array;
    public function provider(): string;
    public function name(): string;
    public function supportsTerminal(string $terminal): bool;
    public function isAllocatable(): bool;
    public function successResponse(): array;
}

支付方式配置控制

GatewayisAllocatable支持终端说明
WechatGatewaytruepc, h5, wechat, weapp, app需DB 配置
AlipayGatewaytruepc, h5, app, aliapp需DB 配置
BalanceGatewayfalse全部终端始终可用
OfflineGatewayfalse全部终端始终可用

开关联动逻辑

  1. channel_profiles.status = 0 → 该终端所有支付方式不可用
  2. channel_payment_configs.status = 0 → 该支付方式在该终端不可用
  3. Gateway::supportsTerminal() = false → 该支付方式不支持该终端
  4. APP_DEBUG = true → 自动启用 wechatpay/alipay 测试模式(Mock SDK 边界)
快速启用:执行 php api/tests/seed_channel_payment.php 插入种子数据,status=1 即启用。

开闭原则:新增支付方式

class StripeGateway extends BaseGateway
{
    public function provider(): string { return 'stripe'; }
    public function name(): string { return 'Stripe支付'; }
    public function supportsTerminal(...): bool { return ...; }
    public function isAllocatable(): bool { return true; }
    public function create(...): array { /* Stripe SDK */ }
    public function notify(...): array { /* 回调验证 */ }
    public function refund(...): array { /* 退款 */ }
    public function refundNotify(...): array { return []; }
    public function successResponse(): array { return []; }
}

$payOrchestrator->register(new StripeGateway(...));
// 无需任何其他修改

应用复用

class MallPayBridge extends BaseCoreService /* 已有 */
{
    public function getOrderPayOptions(...) { /* 验证订单 → 编排器 */ }
    public function createOrderPayment(...) { /* 支付单 → 扣余额 → 编排器 */ }
}

class EduPayBridge extends BaseCoreService /* 将来 */
{
    public function getCoursePayOptions(...) { /* 验证课程 → 同一编排器 */ }
    public function createCoursePayment(...) { /* 选课锁定 → 同一编排器 */ }
}

安全过滤:Security Filter

框架层提供 CoreSecurityFilterService,封装微信小程序内容安全接口(msgSecCheck / imgSecCheck),用于用户生成内容(UGC)的文本和图片安全检测。应用通过注入调用接入,开关关闭时零开销。

职责划分

角色文件职责
框架层api/app/service/domain/security/CoreSecurityFilterService.php对接微信 API、开关控制、降级兜底
应用api/applications/mall/modules/content/core/CoreContentService.php在内容发布流程中注入调用
框架层主动调用api/app/service/domain/media/CoreMediaService.php图片上传时自动检测

核心接口

// api/app/service/domain/security/CoreSecurityFilterService.php
class CoreSecurityFilterService extends BaseCoreService
{
    // 检查 weapp 渠道是否开启内容安全
    public function isEnabled(): bool;

    // 文本安全过滤,违规返回 code=1
    public function filterText(string $content): array;

    // 图片安全过滤,违规返回 code=1
    public function filterImage(string $imagePath): array;
}

开关控制

  • 管理员在后台 渠道管理 → 小程序设置 中切换 content_security_enabled switch
  • 配置存储在 weapp 渠道的 base 配置组中
  • APP_DEBUG = true 时自动 Mock,不调用真实微信 API
  • 微信接口异常时降级放行,不阻塞业务流程

应用集成方式

1. 内容发布时过滤文本(以 mall content 为例):

// api/applications/mall/modules/content/core/CoreContentService.php
use app\service\domain\security\CoreSecurityFilterService;

public function save(array $payload): array
{
    // ... 参数校验 ...

    // 注入安全过滤
    $checkText = $title . ' ' . ((string)($payload['description'] ?? ''));
    $securityResult = $this->app->make(CoreSecurityFilterService::class)
                             ->filterText($checkText);
    if ($securityResult['code'] !== 0) {
        return $securityResult;
    }

    // ... 写库 ...
}

2. 用户昵称变更时过滤:

$result = $this->app->make(CoreSecurityFilterService::class)
                    ->filterText($newNickname);
if ($result['code'] !== 0) {
    return ['code' => 1, 'msg' => $result['msg']];
}

3. 图片上传自动检测:框架层 CoreMediaService::saveUpload() 已内置图片安全检测,上传行为无需额外接入。检测不通过时抛出异常,删除已存储文件。

关键设计决策

决策原因
开关优先检查isEnabled() 在前,关闭时零开销跳过所有 HTTP 调用
异常降级放行微信 API 超时或故障时不阻塞正常业务流程
命名使用 security/filter避免与应用的 content 模块名歧义
返回统一 code / msg 结构应用可直接透传给前端

安装说明

导库方式 框架基础表放在 api/data/install/framework.sql,应用表结构在各自 api/applications/{app}/data/schema.sql,手动导入即可。框架不提供自动建表或 ORM migrate。

数据库导入

1. api/data/install/framework.sql  ← 框架基础表(必导)
2. api/applications/{app}/data/schema.sql     ← 应用表结构(在应用根目录下,自行开发的应用也需要符合一样的SQL命名规范)
3. admin/src/applications/{app}     ← 后台端应用结构

示例应用安装

框架提供了 sample 示例应用,将示例应用前后端源码分别放到 /applications/sample/ 目录后,在后台"应用管理"中点击安装即可自动注册菜单、权限、路由。

步骤 操作
1 将示例应用 copy 到 api/applications/sample/
2 导入 api/data/install/framework.sql(如已导入则跳过)
3 导入 api/applications/sample/data/schema.sql
4 登录后台 → 应用管理 → 点击安装
说明 第三方应用或自定义应用按同样方式放到 api/applications/{app}/,在后台安装即可。

部署说明

宝塔部署 ThinkPHP 8

1. 宝塔创建站点
   - 项目目录:/www/wwwroot/xxx.com/api
   - 运行目录:/www/wwwroot/xxx.com/api/public
   - PHP 版本:PHP 8.0

2. PHP 扩展建议
   - 必装:pdo_mysql、mbstring、curl、fileinfo、openssl、gd、zip
   - 使用 Redis 队列时额外安装:redis

3. 安装服务端依赖
   cd /www/wwwroot/xxx.com/api
   composer install --no-dev

4. 数据库配置
   编辑 api/.env:
   [DATABASE]
   TYPE = mysql
   HOSTNAME = 127.0.0.1
   HOSTPORT = 3306
   DATABASE = onezan
   USERNAME = onezan
   PASSWORD = your-password
   CHARSET = utf8mb4
   PREFIX = onezan_

5. 导库(参见上方安装导库章节)

6. 首次检查
   - 登录后台检查系统设置
   - 菜单、权限、manifest 改动后执行"更新缓存"

伪静态配置

Nginx:
            
# 后台前端 /admin/ 静态站点
location ^~ /admin/ {
    try_files $uri $uri/ /admin/index.html;
}

# 禁止 admin 目录下执行 PHP
location ~ ^/admin/.*\.php$ {
    return 403;
}



# 常规静态资源缓存
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|css|js|woff|woff2|ttf|svg|ico|eot)$
{
    expires 30d;
    access_log off;
    error_log /dev/null;
}

# ThinkPHP 入口
location / {
    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php?s=$1 last;
        break;
    }
}

# PHP 解析  后缀 82 对应 PHP8.2 按你的环境配置对应的
location ~ [^/]\.php(/|$)
{
    try_files $uri =404;
    fastcgi_pass unix:/tmp/php-cgi-82.sock;
    fastcgi_index index.php;
    include fastcgi.conf;
    include pathinfo.conf;
}

# 禁止访问敏感文件
location ~ /\.(htaccess|git|svn|project|env)
{
    return 404;
}

Apache /.htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L]

说明:
- 网站运行目录必须指向 api/public

Redis 与队列设置

1. 队列配置文件
   - api/config/queue.php
   - 控制台命令:php think queue:work

2. 驱动说明
   - sync:本地开发或关闭异步时使用
   - database:推荐的生产方案
   - redis:高并发异步场景使用

3. 后台系统设置中需要维护
   - driver / default_queue / retry_delay_seconds
   - reserve_seconds / max_attempts / work_sleep_seconds

4. 使用 Redis 时补充配置
   - host、port、password、select、timeout、persistent、prefix

Supervisor 长驻示例

[program:onezan_queue_default]
command=/usr/bin/php /www/wwwroot/xxx.com/api/think queue:work --queue=default --sleep=3
directory=/www/wwwroot/xxx.com/api
autostart=true
autorestart=true

说明:
- 驱动为 sync 或队列未启用时不需要启动 worker
- 多队列场景可按 queue 名复制多份 program 配置

生产环境路由缓存

生产环境部署完成后,需编译路由缓存以提升性能:

# 编译路由缓存
php think route:cache

# 添加/删除/安装/卸载应用后,需重新执行
php think route:cache

说明:
- 编译后将所有应用路由合并为单个文件 runtime/route_*.php
- 生产环境自动加载缓存,跳过文件系统扫描
- 源文件修改后缓存自动失效(基于 mtime)

Manifest 校验说明

框架会对所有 manifest.json 进行结构校验:

- 必填字段(code、name、type)缺失或类型错误 → 抛异常
- debug 模式(APP_DEBUG=true):异常直接抛出,便于开发排查
- 生产模式(APP_DEBUG=false):异常记入 error 日志,不中断运行

开发建议:
- 保持 APP_DEBUG=true 直到 manifest 全部修正
- 上线前确认所有应用/模块/插件的 manifest.json 字段完整

前端开发环境

目录:admin/

1. 安装依赖
   npm install

2. 开发配置
   文件:admin/.env.development
   VITE_BASE_URL = /
   VITE_IS_REQUEST_PROXY = true
   VITE_API_URL = http://xxx.com
   VITE_API_URL_PREFIX = /adminapi

3. 本地开发
   npm run dev

前端生产打包

1. 生产配置
   文件:admin/.env.release
   VITE_BASE_URL=/admin/
   VITE_IS_REQUEST_PROXY=false
   VITE_API_URL=
   VITE_API_URL_PREFIX=/adminapi

2. 打包
   npm run build

3. 发布产物
   - 打包目录:admin/dist/
   - 发布目标:api/public/admin/

升级说明

框架升级:api/data/upgrade/framework/YYYYMMDD_xxx.sql
应用升级:api/applications/{app}/data/upgrade/YYYYMMDD_xxx.sql

发生表结构变更时:
1. 同步更新应用 data/schema.sql 基线
2. 补升级 SQL
3. 如涉及菜单/权限/manifest,再更新缓存

技能包使用

项目内置技能包规则,帮助 AI 编码助手按项目结构和规范协同开发。

推荐加载顺序
1. onezan-project-context(项目上下文)
2. onezan-admin-frontend 或 onezan-backend(专项规则)
3. onezan-module(新增模块/插件时)
4. onezan-docs(修改文档时)
5. onezan-delivery-checklist(交付前)
前端使用示例
请先加载 onezan-project-context 和 onezan-admin-frontend。
我在 mall 应用新增 notice 页面和 API 接口,
文件放到 applications/mall/pages、applications/mall/api、applications/mall/router,
页面风格与 goods 页面保持一致,
完成后执行前端构建检查。
后端使用示例
请先加载 onezan-project-context 和 onezan-backend。
我在 mall 应用新增 notice 模块,
包含 manifest、route、controller、service、core、repository、validate、permissions、menus,
同步 schema.sql 与升级 SQL,最后跑接口验证。
目标

让 AI 编码助手按项目实际结构、UI 风格和交付要求协同开发,不偏离规范目录。