时间序列预测:5种简单模型实战指南
2026/4/26 7:39:30
一套 Hyperf 多租户 SaaS 基础框架的开源落地方案,覆盖 从0搭建到持续维护,并给出可直接改造的核心代码骨架。 ---1)先定多租户策略(强烈建议这样起步) 先用 共享库 + 行级隔离(tenant_id),后续再支持“独立库租户”: esc to interrupt - 起步快、成本低、运维简单 - 业务模型统一,适合开源模板 - 后续可按租户等级切换到独立库(VIP 租户) ---2)项目结构(建议) saas-hyperf/ ├─ app/ │ ├─ Context/ │ │ └─ TenantContext.php │ ├─ Middleware/ │ │ └─ TenantResolveMiddleware.php │ ├─ Model/ │ │ ├─ Tenant.php │ │ ├─ User.php │ │ └─ BaseTenantModel.php │ ├─ Service/ │ │ └─ TenantService.php │ ├─ Controller/ │ │ ├─ AuthController.php │ │ └─ ProjectController.php │ └─ Exception/Handler/ ├─ config/autoload/ │ ├─ databases.php │ ├─ middlewares.php │ └─ routes.php ├─ migrations/ ├─ tests/ ├─ .github/workflows/ci.yml ├─ docker-compose.yml ├─ CLAUDE.md ├─ LICENSE └─ README.md ---3)从0初始化(命令)composercreate-project hyperf/hyperf-skeleton saas-hyperfcdsaas-hyperf# 常用依赖(按你项目版本调整)composerrequire hyperf/db-connection hyperf/databasecomposerrequire hyperf/redis hyperf/validationcomposerrequire firebase/php-jwtcomposerrequire--devphpunit/phpunit friendsofphp/php-cs-fixer ---4)核心代码:租户上下文 + 自动隔离4.1TenantContext(协程安全) app/Context/TenantContext.php<?php declare(strict_types=1);namespace App\Context;use Hyperf\Context\Context;class TenantContext{private const KEY_ID='tenant.id';private const KEY_CODE='tenant.code';public staticfunctionset(int$tenantId, string$tenantCode): void{Context::set(self::KEY_ID,$tenantId);Context::set(self::KEY_CODE,$tenantCode);}public staticfunctionid(): ?int{returnContext::get(self::KEY_ID);}public staticfunctioncode(): ?string{returnContext::get(self::KEY_CODE);}public staticfunctionclear(): void{Context::set(self::KEY_ID, null);Context::set(self::KEY_CODE, null);}}---4.2租户解析中间件(子域名 / Header) app/Middleware/TenantResolveMiddleware.php<?php declare(strict_types=1);namespace App\Middleware;use App\Context\TenantContext;use App\Model\Tenant;use Hyperf\Di\Annotation\Inject;use Hyperf\HttpServer\Contract\RequestInterface;use Psr\Http\Message\ResponseInterface;use Psr\Http\Server\MiddlewareInterface;use Psr\Http\Server\RequestHandlerInterface;use Hyperf\HttpServer\Response;class TenantResolveMiddleware implements MiddlewareInterface{#[Inject]protected RequestInterface$request;#[Inject]protected Response$response;publicfunctionprocess($request, RequestHandlerInterface$handler): ResponseInterface{$host=$this->request->getUri()->getHost();$tenantCode=$this->request->header('X-Tenant-Code')??$this->parseSubdomain($host);if(!$tenantCode){return$this->response->json(['code'=>40001,'message'=>'tenant required'])->withStatus(400);}/** @var Tenant|null$tenant*/$tenant=Tenant::query()->where('code',$tenantCode)->where('status',1)->first();if(!$tenant){return$this->response->json(['code'=>40004,'message'=>'tenant not found'])->withStatus(404);}TenantContext::set((int)$tenant->id,(string)$tenant->code);try{return$handler->handle($request);}finally{TenantContext::clear();}}privatefunctionparseSubdomain(string$host): ?string{// 例如 tenant1.example.com$parts=explode('.',$host);returncount($parts)>=3?$parts[0]:null;}}---4.3基础租户模型(自动 tenant_id 过滤) app/Model/BaseTenantModel.php<?php declare(strict_types=1);namespace App\Model;use App\Context\TenantContext;use Hyperf\DbConnection\Model\Model;use Hyperf\Database\Model\Builder;abstract class BaseTenantModel extends Model{protected staticfunctionbooted(): void{static::addGlobalScope('tenant_scope',function(Builder$builder){$tenantId=TenantContext::id();if($tenantId!==null){$builder->where($builder->getModel()->getTable().'.tenant_id',$tenantId);}});static::creating(function(Model$model){if(empty($model->tenant_id)&&TenantContext::id()!==null){$model->tenant_id=TenantContext::id();}});}}业务模型示例: app/Model/Project.php<?php declare(strict_types=1);namespace App\Model;class Project extends BaseTenantModel{protected ?string$table='projects';protected array$fillable=['tenant_id','name','owner_id','status',];}---5)关键数据表(迁移) migrations/2026_01_01_000001_create_core_tables.php<?php use Hyperf\Database\Schema\Schema;use Hyperf\Database\Schema\Blueprint;use Hyperf\Database\Migrations\Migration;returnnew class extends Migration{publicfunctionup(): void{Schema::create('tenants',function(Blueprint$table){$table->bigIncrements('id');$table->string('name',100);$table->string('code',64)->unique();// 子域名/租户唯一编码$table->tinyInteger('status')->default(1);$table->timestamps();});Schema::create('users',function(Blueprint$table){$table->bigIncrements('id');$table->unsignedBigInteger('tenant_id');$table->string('email',191);$table->string('password',255);$table->string('name',100);$table->timestamps();$table->unique(['tenant_id','email']);$table->index(['tenant_id']);});Schema::create('projects',function(Blueprint$table){$table->bigIncrements('id');$table->unsignedBigInteger('tenant_id');$table->unsignedBigInteger('owner_id');$table->string('name',191);$table->tinyInteger('status')->default(1);$table->timestamps();$table->index(['tenant_id','owner_id']);});}publicfunctiondown(): void{Schema::dropIfExists('projects');Schema::dropIfExists('users');Schema::dropIfExists('tenants');}};---6)路由与中间件注册 config/autoload/middlewares.php<?phpreturn['http'=>[\App\Middleware\TenantResolveMiddleware::class,],];config/autoload/routes.php<?php use Hyperf\HttpServer\Router\Router;Router::addGroup('/api',function(){Router::post('/auth/login',[App\Controller\AuthController::class,'login']);Router::get('/projects',[App\Controller\ProjectController::class,'index']);Router::post('/projects',[App\Controller\ProjectController::class,'store']);});---7)控制器示例(天然按 tenant 隔离) app/Controller/ProjectController.php<?php declare(strict_types=1);namespace App\Controller;use App\Model\Project;use Hyperf\HttpServer\Contract\RequestInterface;use Hyperf\HttpServer\Contract\ResponseInterface;class ProjectController{publicfunctionindex(ResponseInterface$response){$list=Project::query()->orderByDesc('id')->limit(20)->get();return$response->json(['code'=>0,'data'=>$list]);}publicfunctionstore(RequestInterface$request, ResponseInterface$response){$data=$request->all();$project=Project::query()->create(['name'=>$data['name']??'','owner_id'=>(int)($data['owner_id']??0),'status'=>1,]);return$response->json(['code'=>0,'data'=>$project]);}}---8)开源必须补齐的工程化8.1Docker(本地一键跑) docker-compose.yml 至少包含: - app(hyperf) - mysql - redis8.2CI(GitHub Actions) .github/workflows/ci.yml: - PHP lint - 单元测试 - 静态分析(phpstan/psalm 任选) - 格式检查(php-cs-fixer)8.3开源治理 - LICENSE:推荐 MIT / Apache-2.0 - README.md:安装、配置、租户接入流程、API 示例 - issue templates / PR template - SECURITY.md:漏洞提交通道 - 版本规范:SemVer(v0.x ->v1.0) ---9)持续维护路线(你可以直接按这个迭代) v0.1(MVP) - 租户识别 - 行级隔离 - 用户/项目基础 CRUD - JWT 登录 v0.2 - RBAC(tenant_admin / member) - 审计日志(谁在何时改了什么) - 限流(按租户) v0.3 - 计费模型(套餐、成员上限、资源配额) - Webhook / OpenAPI v1.0 - 独立库租户能力 - 数据迁移工具(共享库 ->独立库) - 多环境发布标准化(灰度/回滚) ---10)你这个开源框架最容易踩坑的5点1. 忘记给查询加租户过滤(上面全局作用域已处理大部分)2. 超级管理员接口误穿透租户边界(必须显式 withoutGlobalScope 且审计)3. 唯一索引没带 tenant_id(导致跨租户冲突)4. 缓存 key 没加租户前缀(数据串租户)5. 异步任务没带 tenant 上下文(队列 payload 必须含 tenant_id) --- 如果你要直接发布模板仓库,这套骨架已经够做一个可运行的开源起点:先把上面4个核心文件 + 迁移 + CI 放进仓库,第一版就能被社区用起来并持续迭代。