xiaosongshu / fasterphpweb
一个常驻内存 php框架,提供http,rtmp,websocket服务
Installs: 14
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 2
Forks: 0
Open Issues: 0
Type:project
Requires
- php: >=8.1
- ext-gd: *
- ext-mbstring: *
- ext-memcache: *
- ext-mysqli: *
- ext-pcntl: *
- ext-posix: *
- ext-readline: *
- ext-sockets: *
- ext-zlib: *
- apix/log: ^1.2
- doctrine/annotations: ^1.14
- elasticsearch/elasticsearch: 7.17.*
- evenement/evenement: ^3.0
- haozi-team/chatgpt-php: ^2.1
- jeremeamia/superclosure: ^2.4@dev
- monolog/monolog: ^2.9
- nikic/fast-route: ^2.0@dev
- nikic/php-parser: ^4.19
- nmred/kafka-php: 0.2.*
- php-amqplib/php-amqplib: 3.2.*
- php-di/php-di: ^6
- phpseclib/phpseclib: *
- psr/container: ^1.1.1
- react/event-loop: ^3@dev
- react/promise: ^2.9
- symfony/yaml: 5.4.*
- xiaosongshu/colorword: dev-master
- xiaosongshu/mail: dev-master
- xiaosongshu/nacos: dev-master
- xiaosongshu/progress: dev-master
- xiaosongshu/table: dev-master
- dev-master
- v5.3.7
- v5.3.6
- v5.3.5
- v5.3.4
- v5.3.3
- v5.3.2
- v5.3.1
- v5.3.0
- v5.2.46
- v5.2.45
- v5.2.44
- v5.2.43
- v5.2.42
- v5.2.41
- v5.2.40
- v5.2.34
- v5.2.33
- v5.2.32
- v5.2.31
- v5.2.3
- v5.2.2
- v5.2.1
- v5.2.0
- v5.1.9
- v5.1.8
- v5.1.7
- v5.1.6
- v5.1.5
- v5.1.4
- v5.1.3
- v5.1.2
- v5.1.1
- v5.1.0
- v5.0.1
- v5.0.0
- v4.4.4
- v4.4.3
- v4.4.2
- v4.4.1
- v4.4.0
- v4.3.9
- v4.3.8
- v4.3.7
- v4.3.6
- v4.3.5
- v4.3.4
- v4.3.3
- v4.3.2
- v4.3.1
- v4.3.0
- v4.2.3
- v4.2.2
- v4.2.1
- v4.2.0
- v4.1.5
- v4.1.4
- v4.1.3
- v4.1.2
- v4.1.1
- v4.1.0
- v4.0.5
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0.0
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.0.1
- v2.0.0
- dev-test
- dev-yii
- dev-phar
- dev-backup
- dev-rtmp
This package is auto-updated.
Last update: 2024-11-22 10:44:41 UTC
README
一个使用socket编写的PHP常驻内存框架,本项目是想到哪里写到哪里,里面有记录了笔记。
框架简介
本框架旨在让用户了解和学习PHP实现web运行原理,涉及到了相对较多的底层基础知识。
fasterphpweb是一款常驻内存的轻量级的php框架,遵循常用的mvc架构。
本框架对timer,redis,mysql,rabbitmq,kafka,websocket,elasticsearch,nacos,sqlite 进行了简单封装,并且保留了部分代码实例。
本框架提供基本的http服务,可支持api接口或者web网页。
本框架提供基本的websocket服务,可支持长链接,适用于聊天等场景。
本框架提供rtmp流媒体服务,纯PHP开发,不需要其他依赖。支持rtmp推流,rtmp拉流和flv拉流。适用于直播场景。
本框架不对外提供服务了,仅作为作者个人学习项目,因为里面添加太多的笔记了。而且代码发生了很多变化,也添加很多第三方的插件,不能算纯原创了。
项目安装
composer create-project xiaosongshu/fasterphpweb
启动项目
linux环境
开启 php start.php start -d 关闭 php start.php stop
windows环境
开启 php windows.php 关闭 php windows.php stop
目录结构
|-- app
|-- controller <控制层>
|-- index <index业务模块>
|-- ... <其他业务模块>
|-- facade <门面模块>
|-- queue <队列任务模块>
|-- rabbitmq <rabbitmq队列>
|-- model <模型层>
|-- command <自定义命令行>
|-- config <配置项>
|--app.php <项目配置>
|--database.php <数据库配置>
|--redis.php <缓存配置>
|--server.php <http服务配置>
|--timer.php <定时任务配置>
|-- mysql <mysql文件,非必须>
...
|-- public <公共文件>
|-- root <系统目录,建议不要轻易改动>
...
|-- vendor <外部扩展包>
|-- view <视图层>
...
|-- composer.json <项目依赖>
|-- README.md <项目说明文件>
|-- start.php <服务启动文件>
|-- songshu <服务启动文件>
快速开始
1,导入mysql文件到你的数据库或者自己创建
2,进入项目根目录:cd /your_project_root_path
3,调试模式: php start.php start
4,守护进程模式: php start.php start -d
5,重启项目: php start.php restart
6,停止项目: php start.php stop
7,项目默认端口为:8000, 你可以自行修改
8,项目访问地址:localhost://127.0.0.1:8000
9,windows默认只开启一个http服务
10,windows若需要测试队列,请单独开启一个窗口执行 php start.php queue ,监听队列
11,windows不支持定时器
12,本项目支持普通的redis的list队列,同时支持rabbitmq队列,如果需要使用延时队列,需要安装插件
13,在windows上默认使用select的io多路复用模型,在linux上默认使用epoll的io多路复用模型
14,但是在linux系统上,如果使用开启后台运行,加入不支持epoll模型,则使用的多进程同步阻塞io模型。
15,系统环境搭建,默认需要php,mysql,redis,而rabbitmq不是必须的。你可以自己搭建所需要的环境,也可以 使用本项目下面的docker配置。
16,假设你使用docker配置,首先要安装docker,然后执行命令:docker-compose up -d 启动环境。注意修改 docker-compose.yaml 里面的目录映射,端口映射。
注意
1,原则上本项目只依赖socket,mysqli,redis扩展和pcntl系列函数,如需要第三方扩展,请自行安装。
2,因为是常驻内存,所以每一次修改了php代码后需要重启项目。
3,start.php为项目启动源码,root目录为运行源码,除非你已经完全明白代码意图,否则不要轻易修改代码。
4,所有的控制器方法都必须返回一个字符串,否则连接一直占用进程,超时后系统自动断开连接。
5,业务代码不要使用sleep,exit这两个方法。否则导致整个进程阻塞或者中断。
联系开发者
项目地址
https://github.com/2723659854/fasterphpweb
项目文档
项目基本配置
# 请在config/server.php 当中配置项目的端口和进程数 <?php return [ //监听端口 'num'=>4,//启动进程数,建议不要超过CPU核数的两倍,windows无效 'port'=>8000,//http监听端口 ];
控制层
# 注意命名空间 /** 这里表示admin模块 */ namespace App\Controller\Admin; use APP\Facade\Cache; use APP\Facade\User; /** * @purpose 类名 这里表示index控制器 * @author 作者名称 * @date 2023年4月27日16:05:11 * @note 注意事项 */ class Index { /** * @method get|post 本项目没有提供强制路由,自动根据模块名/控制器名/方法名 解析 * */ public function index() { return '/admin/index/index'; } }
请求
/** * request以及模板渲染演示 * @param Request $request * @return array|false|string|string[] */ public function database(Request $request) { /** 获取var参数 */ $var = $request->get('var'); $name = $request->post('name'); $all = $request->all(); /** 调用数据库 */ $data = User::where('username', '=', 'test')->first(); /** 读取配置 */ $app_name = config('app')['app_name']; /** 模板渲染 参数传递 */ return view('index/database', ['var' => $var, 'str' => date('Y-m-d H:i:s'), 'user' => json_encode($data), 'app_name' => $app_name]); }
获取get参数
/** 获取所有get参数 */ $data = $request->get(); /** 获取指定键名参数 */ $name = $request->get('name','tom');
获取post参数
/** 获取所有post请求参数 */ $data = $request->post(); /** 获取指定键名参数 */ $name = $request->post('name','tom');
获取所有请求参数
$data = $request->all();
获取原始请求包体
$post = $request->rawBody();
获取header头部信息
/** 获取所有的header */ $request->header(); /** 获取指定的header参数host */ $request->header('host');
获取原始querystring
$request->queryString()
获取cookie
/** 获取cookie */ $request->cookie('username'); /** 获取cookie 并设置默认值 */ $request->cookie('username', 'zhangsan');
响应
设置cookie
return \response()->cookie('zhangsan','tom');
返回视图
return view('index/database', ['var' => $var, 'str' => date('Y-m-d H:i:s'), 'user' => json_encode($data), 'app_name' => $app_name]);
返回数据
return response(['status'=>200,'msg'=>'ok','data'=>$data]); /** 会覆盖response里面的数据 */ return response()->withBody('返回的数据');
重定向
return redirect('/admin/user/list');
下载文件
/** 直接下载 */ return response()->file(public_path().'/favicon.ico'); /** 设置别名 */ return response()->download(public_path().'/favicon.ico','demo.ico');
设置响应头
return \response(['status'=>200,'msg'=>'ok','data'=>$data],200,['Content-Type'=>'application/json']); return \response(['status'=>200,'msg'=>'ok','data'=>$data])->header('Content-Type','application/json'); return \response(['status'=>200,'msg'=>'ok','data'=>$data])->withHeader('Content-Type','application/json'); return \response(['status'=>200,'msg'=>'ok','data'=>$data])->withHeaders(['Content-Type'=>'application/json']);
设置响应状态码
return response([],200); return response([])->withStatus(200);
模板渲染
默认支持html文件,变量使用花括号表示{},暂不支持for,foreach,if等复杂模板运算
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试模板</title> </head> <body> <h2> 一个常驻内存的PHP轻量级框架socketweb </h2> <img src="/head.png" alt="头像"> <h5>{$var}</h5> <h5>数据库查询的数据</h5> <h5>{$user}</h5> <h5>APP_NAME:{$app_name}</h5> </body> </html>
模型层
默认使用mysql数据库
数据库配置
# 在config/database.php 当中配置 <?php return [ /** 默认连接方式 */ 'default'=>'mysql', /** mysql配置 */ 'mysql'=>[ 'host'=>'192.168.4.105', 'username'=>'root', 'passwd'=>'root', 'dbname'=>'go_gin_chat', 'port'=>'3306' ], //todo 其他类型请自己去实现 ];
模型的定义
<?php /** 命名空间 */ namespace App\Model; /** 引入需要继承的模型基类 */ use Root\Model; /** 定义模型名称 并继承模型基类 */ class Book extends Model { /** @var string $table 建议指定表名,否则系统根据模型名推断表名,可能会不准确 */ public $table = 'messages'; }
模型的使用
/** 测试数据库 */ public function query() { /** 第1种方法:使用门面类调用模型,需要自己创建门面类 */ $messages = Fbook::where('id', '=', 3)->first(); /** 第2种方法:直接静态化调用 */ $next = Book::where('id','=',1)->first(); return ['status' => 1, 'data' => $messages, 'msg' => 'success','book'=>$book]; }
数据库事务的使用
use App\Model\Admin; /** 开启事务 */ $transAction = Admin::startTransaction(); try { /** 如果查询报错,那么写入就会失败 */ $query = Admin::where('id','=',1398)->first(); var_dump($query); Admin::insert([ 'phone'=>'125896325', 'nickname'=>'tom', 'password'=>md5(time()) ]); /** 提交事务 */ $transAction->commit(); }catch (\Exception $exception){ /** 发生了异常,事务回滚 */ $transAction->rollback(); /** 打印报错信息 */ var_dump($exception->getMessage()); }
提示:无论在使用何种框架操作事务的时候,尽量缩小事务的范围,数据库操作要和开启事务,提交紧紧的贴在一起,中间尽量不要有其他的 业务逻辑代码,避免锁表,避免多个事务交叉锁死。
缓存的使用
项目默认支持redis缓存
缓存的配置
# 请在config/redis.php 当中配置 <?php return [ /** redis队列开关 */ 'enable'=>false, /** redis连接基本配置 */ //'host' => 'redis', 'host' => '192.168.4.105', 'password' => '', 'port' => '6379', 'database' => 0, ];
缓存的使用
/** 测试缓存 */ public function cache() { /** 第1种方法,直接静态方法调用 */ Cache::set('happy','new year'); return ['code' => 200, 'msg' => 'ok','静态调用'=>Cache::get('happy')]; }
路由
配置文件
# config/route.php <?php return [ /** 首页 */ ['GET', '/', [App\Controller\Index\Index::class, 'index']], /** 路由测试 */ ['GET', '/index/demo/index', [\App\Controller\Admin\Index::class, 'index']], /** 上传文件 */ ['GET', '/upload', [\App\Controller\Admin\Index::class, 'upload']], /** 保存文件 */ ['post', '/store', [\App\Controller\Admin\Index::class, 'store']], /** 缓存存取 */ ['get', '/cache', [\App\Controller\Index\Index::class, 'cache']], /** 返回json */ ['get', '/json', [\App\Controller\Index\Index::class, 'json']], /** 数据库 */ ['get', '/database', [\App\Controller\Index\Index::class, 'database']], /** 数据库写入 */ ['get', '/insert', [\App\Controller\Index\Index::class, 'insert']], /** base64 文件上传 */ ['get', '/base64', [\App\Controller\Index\Index::class, 'upload']], /** base64 文件保存 */ ['post', '/base64_store', [\App\Controller\Index\Index::class, 'store']], /** 测试redis队列 */ ['get', '/queue', [\App\Controller\Index\Index::class, 'queue']], /** 测试rabbitmq队列 */ ['get', '/rabbitmq', [\App\Controller\Index\Index::class, 'rabbitmq']], /** 文件下载 */ ['get', '/download', [\App\Controller\Index\Index::class, 'download']], /** 测试门面类facade */ ['get', '/facade', [\App\Controller\Index\Index::class, 'facade']], /** 测试es搜索 */ ['get', '/es', [\App\Controller\Index\Index::class, 'elasticsearch']], /** 测试中间件 */ ['GET','/middle',[\App\Controller\Index\Index::class,'middle'],[\App\Middleware\MiddlewareA::class,\App\Middleware\MiddlewareB::class]] ];
注解路由
/** * 测试注解路由 * @param Request $request * @return Response */ #[RequestMapping(methods:'get',path:'/login')] public function login(Request $request):Response{ return \response(['I am a RequestMapping !']); } /** * 测试注解路由和中间件 * @param Request $request * @return Response */ #[RequestMapping(methods:'get,post',path:'/chat'),Middlewares(MiddlewareA::class)] public function chat(Request $request):Response{ return \response('我是用的注解路由'); }
中间件
创建中间件
php songshu make:middleware Auth php start.php make:middleware Auth
中间件内容如下:
<?php namespace App\Middleware; use Root\Lib\MiddlewareInterface; use Root\Request; use Root\Response; /** * @purpose 中间件 * @author administrator * @time 2023-09-28 05:51:21 */ class Auth implements MiddlewareInterface { public function process(Request $request, callable $next):Response { //todo 这里处理你的逻辑 return $next($request); } }
使用中间件
1,路由
/** 测试中间件 */ ['GET','/middle',[\App\Controller\Index\Index::class,'middle'],[\App\Middleware\MiddlewareA::class,\App\Middleware\MiddlewareB::class]]
2,注解
/** * 测试注解路由和中间件 * @param Request $request * @return Response */ #[RequestMapping(methods:'get,post',path:'/chat'),Middlewares(MiddlewareA::class,Auth::class)] public function chat(Request $request):Response{ return \response('我是用的注解路由'); }
依赖自动注入
本框架提供依赖自动注入,不需要每一次都手动实例化依赖。使用关键字@Inject,系统根据关键字自动注入依赖,详见下面的方法。
<?php namespace App\Controller\Index; use App\Service\HahaService; use Root\Annotation\Mapping\RequestMapping; use Root\Request; use Root\Response; /** * @purpose 控制器 * @author administrator * @time 2023-10-11 07:02:03 */ class Video { /** * @Inject * @var HahaService 测试服务注解 */ public HahaService $hahaService; /** * 测试注解 * @param Request $request * @return Response */ #[RequestMapping(methods:'get',path:'/video/inject')] public function testInject(Request $request):Response{ return \response($this->hahaService->back()); } }
这里使用了注解,系统自动注入依赖App\Service\HahaService,不需要手动注入(使用__construct()方法注入依赖)。
定时器
只能在linux系统中使用定时器,或者使用docker环境。
添加定时任务
//第一种方式 /** 使用回调函数投递定时任务 */ $first = Timer::add('5',function ($username){ echo date('Y-m-d H:i:s'); echo $username."\r\n"; },['投递的定时任务'],true); echo "定时器id:".$first."\r\n"; /** 根据id删除定时器 */ Timer::delete($first); /** 使用数组投递定时任务 */ Timer::add('5',[\Process\CornTask::class,'say'],['投递的定时任务'],true); /** 获取所有正在运行的定时任务 */ print_r(Timer::getAll()); /** 清除所有定时器 */ Timer::deleteAll(); //第二种,使用配置文件config/timer.php return [ /** 定时器 */ 'one'=>[ /** 是否开启 */ 'enable'=>true, /** 回调函数,调用静态方法 */ 'function'=>[Process\CornTask::class,'handle'], /** 周期 */ 'time'=>3, /** 是否循环执行 */ 'persist'=>true, ], 'two'=>[ 'enable'=>false, /** 调用动态方法 */ 'function'=>[Process\CornTask::class,'say'], 'time'=>5, 'persist'=>true, ], 'three'=>[ 'enable'=>true, /** 调用匿名函数 */ 'function'=>function(){$time=date('y-m-d H:i:s'); echo "\r\n {$time} 我是随手一敲的匿名函数!\r\n";}, 'time'=>5, 'persist'=>true, ] ];
redis 队列
redis连接配置
<?php # /config/redis.php return [ /** 是否提前启动缓存连接 */ 'preStart'=>false, /** redis队列开关 */ 'enable'=>true, /** redis连接基本配置 */ 'host' => 'redis', //'host' => '192.168.4.105', 'password' => 'xT9=123456', 'port' => '6379', 'database' => 0, ];
创建消费者
php songshu make:queue Demo
生成的消费者内容如下:
<?php namespace App\Queue; use Root\Queue\Queue; /** * @purpose redis消费者 * @author administrator * @time 2023-10-31 03:44:50 */ class Demo extends Queue { public $param=null; /** * Test constructor. * @param array $param 根据业务需求,传递业务参数,必须以一个数组的形式传递 */ public function __construct(array $param) { $this->param=$param; } /** * 消费者 * 具体的业务逻辑必须写在handle里面 */ public function handle(){ //todo 这里写你的具体的业务逻辑 var_dump($this->param); } }
投递消息
/** 普通队列消息 */ \App\Queue\Demo::dispatch(['name' => 'hanmeimei', 'age' => '58']); /** 延迟队列消息 ,单位秒(s)*/ \App\Queue\Demo::dispatch(['name' => '李磊', 'age' => '32'], 3);
rabbitmq消息队列
rabbitmq连接配置
<?php return [ /** rabbitmq基本连接配置 */ 'host'=>'faster-rabbitmq', 'port'=>'5672', 'user'=>'guest', 'pass'=>'guest', ];
创建消费者类
php start.php make:rabbitmq DemoConsume
生成的消费者内容如下:
<?php namespace App\Rabbitmq; use Root\Queue\RabbitMQBase; /** * @purpose rabbitMq消费者 * @author administrator * @time 2023-10-31 05:27:48 */ class DemoConsume extends RabbitMQBase { /** * 自定义队列名称 * @var string */ public $queueName ="DemoConsume"; /** @var int $timeOut 普通队列 */ public $timeOut=0; /** * 逻辑处理 * @param array $param * @return void */ public function handle(array $param) { // TODO: Implement handle() method. } /** * 异常处理 * @param \Exception|\RuntimeException $exception * @return mixed|void */ public function error(\Exception|\RuntimeException $exception) { // TODO: Implement error() method. } }
开启消费者任务
# config/rabbitmqProcess.php <?php return [ /** 队列名称 */ 'demoForOne'=>[ /** 消费者名称 */ 'handler'=>App\Rabbitmq\Demo::class, /** 进程数 */ 'count'=>2, /** 是否开启消费者 */ 'enable'=>false, ], /** 队列名称 */ 'demoForTwo'=>[ /** 消费者名称 */ 'handler'=>App\Rabbitmq\Demo2::class, /** 进程数 */ 'count'=>1, /** 是否开启消费者 */ 'enable'=>true, ], 'DemoConsume'=>[ /** 消费者名称 */ 'handler'=>App\Rabbitmq\DemoConsume::class, /** 进程数 */ 'count'=>1, /** 是否开启消费者 */ 'enable'=>true, ] ];
投递消息
(new DemoConsume())->publish(['status'=>1,'msg'=>'ok']);
若不满足需求,可以使用插件
composer require xiaosongshu/rabbitmq
elasticsearch 搜索
use root\ESClient; /** 测试elasticsearch用法 */ public function search() { /** 实例化es客户端 */ $client = new ESClient(); /** 查询节点的所有数据 */ return $client->all('v2_es_user3','_doc'); } # 其他用法参照 root\ESClient::class的源码,
elasticsearch 支持的方法
创建索引:createIndex 创建表结构:createMappings 删除索引:deleteIndex 获取索引详情:getIndex 新增一行数据:create 批量写入数据:insert 根据id批量删除数据:deleteMultipleByIds 根据Id 删除一条记录:deleteById 获取表结构:getMap 根据id查询数据:find 根据某一个关键字搜索:search 使用原生方式查询es的数据:nativeQuerySearch 多个字段并列查询,多个字段同时满足需要查询的值:andSearch or查询 多字段或者查询:orSearch 根据条件删除数据:deleteByQuery 根据权重查询:searchByRank 获取所有数据:all 添加脚本:addScript 获取脚本:getScript 使用脚本查询:searchByScript 使用脚本更新文档:updateByScript 索引是否存在:IndexExists 根据id更新数据:updateById
若不满足需求,可以使用插件
composer require xiaosongshu/elasticsearch
一些例子:
<?php require_once 'vendor/autoload.php'; /** 实例化客户端 */ $client = new \Xiaosongshu\Elasticsearch\ESClient([ /** 节点列表 */ 'nodes' => ['192.168.4.128:9200'], /** 用户名 */ 'username' => '', /** 密码 */ 'password' => '', ]); /** 删除索引 */ $client->deleteIndex('index'); /** 如果不存在index索引,则创建index索引 */ if (!$client->IndexExists('index')) { /** 创建索引 */ $client->createIndex('index', '_doc'); } /** 创建表 */ $result = $client->createMappings('index', '_doc', [ 'id' => ['type' => 'long',], 'title' => ['type' => 'text', "fielddata" => true,], 'content' => ['type' => 'text', 'fielddata' => true], 'create_time' => ['type' => 'text'], 'test_a' => ["type" => "rank_feature"], 'test_b' => ["type" => "rank_feature", "positive_score_impact" => false], 'test_c' => ["type" => "rank_feature"], ]); /** 获取数据库所有数据 */ $result = $client->all('index','_doc',0,15); /** 写入单条数据 */ $result = $client->create('index', '_doc', [ 'id' => rand(1,99999), 'title' => '我只是一个测试呢', 'content' => '123456789', 'create_time' => date('Y-m-d H:i:s'), 'test_a' => 1, 'test_b' => 2, 'test_c' => 3, ]); /** 批量写入数据 */ $result = $client->insert('index','_doc',[ [ 'id' => rand(1,99999), 'title' => '我只是一个测试呢', 'content' => '你说什么', 'create_time' => date('Y-m-d H:i:s'), 'test_a' => rand(1,10), 'test_b' => rand(1,10), 'test_c' => rand(1,10), ], [ 'id' => rand(1,99999), 'title' => '我只是一个测试呢', 'content' => '你说什么', 'create_time' => date('Y-m-d H:i:s'), 'test_a' => rand(1,10), 'test_b' => rand(1,10), 'test_c' => rand(1,10), ], [ 'id' => rand(1,99999), 'title' => '我只是一个测试呢', 'content' => '你说什么', 'create_time' => date('Y-m-d H:i:s'), 'test_a' => rand(1,10), 'test_b' => rand(1,10), 'test_c' => rand(1,10), ], ]); /** 使用关键字搜索 */ $result = $client->search('index','_doc','title','测试')['hits']['hits']; /** 使用id更新数据 */ $result1 = $client->updateById('index','_doc',$result[0]['_id'],['content'=>'今天你测试了吗']); /** 使用id 删除数据 */ $result = $client->deleteById('index','_doc',$result[0]['_id']); /** 使用条件删除 */ $client->deleteByQuery('index','_doc','title','测试'); /** 使用关键字搜索 */ $result = $client->search('index','_doc','title','测试')['hits']['hits']; /** 使用条件更新 */ $result = $client->updateByQuery('index','_doc','title','测试',['content'=>'哇了个哇,这么大的种子,这么大的花']); /** 添加脚本 */ $result = $client->addScript('update_content',"doc['content'].value+'_'+'谁不说按家乡好'"); /** 添加脚本 */ $result = $client->addScript('update_content2',"(doc['content'].value)+'_'+'abcdefg'"); /** 获取脚本内容 */ $result = $client->getScript('update_content'); /** 使用脚本搜索 */ $result = $client->searchByScript('index', '_doc', 'update_content', 'title', '测试'); /** 删除脚本*/ $result = $client->deleteScript('update_content2'); /** 使用id查询 */ $result = $client->find('index','_doc','7fitkYkBktWURd5Uqckg'); /** 原生查询 */ $result = $client->nativeQuerySearch('index',[ 'query'=>[ 'bool'=>[ 'must'=>[ [ 'match_phrase'=>[ 'title'=>'测试' ], ], [ 'script'=>[ 'script'=>"doc['content'].value.length()>2" ] ] ] ] ] ]); /** and并且查询 */ $result = $client->andSearch('index','_doc',['title','content'],'测试'); /** or或者查询 */ $result = $client->orSearch('index','_doc',['title','content'],'今天');
你可能需要一键搭建elasticsearch服务,仅供参考:
docker run -d --name my-es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.9.2
elasticsearch属于内存数据库,启动服务后会占用很大的系统内存(redis和sqlite这两个和elasticsearch不是一个数量级),导致服务器卡顿,影响其他服务正常运行,所以将elasticsearch独立搭建服务。
正式生成环境建议单独部署一台服务器用于部署elasticsearch,如果需要多个节点,那就需要多部署几台服务器。
如果有特殊的分词需求,建议安装扩展ik分词器,参照Docker中的elasticsearch安装ik分词器
加入容器
解放双手,不需要每一次都去实例化需要调用的对象。使用容器简单方便。
/** G方法: */ G(App\Service\DemoService::class)->talk(1); /** M方法:*/ M(App\Service\DemoService::class)->talk(1);
G方法和M方法的区别是:
G方法只会实例化一次对象,然后存储在内存中,下一次调用直接从内存中获取。
而M方法每一次都是重新实例化一个新的对象。
自定义命令
创建
php start.php make:command Demo
生成的自定义命令类如下:
<?php namespace App\Command; use Root\Lib\BaseCommand; /** * @purpose 用户自定义命令 * @author administrator * @time 2023-10-10 09:11:34 */ class Demo extends BaseCommand { /** @var string $command 命令触发字段,请替换为你自己的命令,执行:php start.php your:command */ public $command = 'Demo'; /** * 配置参数 * @return void */ public function configure(){ /** 必选参数 */ $this->addArgument('argument','这个是参数argument的描述信息'); /** 可传参数 */ $this->addOption('option','这个是option参数的描述信息'); } /** * 清在这里编写你的业务逻辑 * @return void */ public function handle() { /** 获取必选参数 */ var_dump($this->getOption('argument')); /** 获取可选参数 */ var_dump($this->getOption('option')); $this->info("请在这里编写你的业务逻辑"); } }
sqlite数据库支持
创建模型
php start.php make:sqlite Talk
或者
php songshu make:sqlite Talk
模型内容如下
<?php namespace App\SqliteModel; use Root\Lib\SqliteBaseModel; /** * @purpose sqlite数据库 * @note 示例 */ class Talk extends SqliteBaseModel { /** 存放目录:请修改为你自己的字段,真实路径为config/sqlite.php里面absolute设置的路径 + $dir ,例如:/usr/src/myapp/fasterphpweb/sqlite/datadir/hello/talk */ public string $dir = 'hello/talk'; /** 表名称:请修改为你自己的表名称 */ public string $table = 'talk'; /** 表字段:请修改为你自己的字段 */ public string $field ='id INTEGER PRIMARY KEY,name varhcar(24),created text(12)'; }
用法
<?php use App\SqliteModel\Talk; /** 写入数据 */ var_dump(Talk::insert(['name' => 'hello', 'created' => time()])); /** 更新数据 */ var_dump(Talk::where([['id', '>=', 1]])->update(['name' => 'mm'])); /** 查询1条数据 */ var_dump(Talk::where([['id', '>=', 1]])->select(['name'])->first()); /** 删除数据 */ var_dump(Talk::where([['id', '=', 1]])->delete()); /** 统计 */ var_dump(Talk::where([['id', '>', 1]])->count()); /** 查询多条数据并排序分页 */ $res = Talk::where([['id', '>', 0]]) ->orderBy(['created'=>'asc']) ->page(1, 10) ->get(); ?>
nacos服务
安装客户端
composer require xiaosongshu/nacos
nacos提供的方法
require_once __DIR__.'/vendor/autoload.php'; $dataId = 'CalculatorService'; $group = 'api'; $serviceName = 'mother'; $namespace = 'public'; $client = new \Xiaosongshu\Nacos\Client('http://127.0.0.1:8848','nacos','nacos'); /** 发布配置 */ print_r($client->publishConfig($dataId, $group, json_encode(['name' => 'fool', 'bar' => 'ha']))); /** 获取配置 */ print_r($client->getConfig($dataId, $group,'public')); /** 监听配置 */ print_r($client->listenerConfig($dataId, $group, json_encode(['name' => 'fool', 'bar' => 'ha']))); /** 删除配置 */ print_r($client->deleteConfig($dataId, $group)); /** 创建服务 */ print_r($client->createService($serviceName, $namespace, json_encode(['name' => 'tom', 'age' => 15]))); /** 创建实例 */ print_r($client->createInstance($serviceName, "192.168.4.110", '9504', $namespace, json_encode(['name' => 'tom', 'age' => 15]), 99, 1, false)); /** 获取服务列表 */ print_r($client->getServiceList($namespace)); /** 服务详情 */ print_r($client->getServiceDetail($serviceName, $namespace)); /** 获取实例列表 */ print_r($client->getInstanceList($serviceName, $namespace)); /** 获取实例详情 */ print_r($client->getInstanceDetail($serviceName, false, '192.168.4.110', '9504')); /** 更新实例健康状态 */ print_r($client->updateInstanceHealthy($serviceName, $namespace, '192.168.4.110', '9504',false)); /** 发送心跳 */ print_r($client->sendBeat($serviceName, '192.168.4.110', 9504, $namespace, false, 'beat')); /** 移除实例*/ print_r($client->removeInstance($serviceName, '192.168.4.110', 9504, $namespace, false)); /** 删除服务 */ print_r($client->removeService($serviceName, $namespace));
可以根据自己的需求,给项目添加配置检测,微服务管理。
配置检测:创建一个常驻内存进程,每隔30秒,读取一次nacos服务器上的配置,配置发生了变化,则修改配置,并重启服务。
微服务管理:创建一个常驻内存进程,进程启动的时候注册服务。
nacos配置管理
配置nacos服务器,以及开启配置管理
# config/nacos.php <?php return [ /** 使用nacos自动管理配置 */ /** 是否开启配置管理 */ 'enable' => true, /** nacos服务器ip */ 'host' => '192.168.4.98', /** nacos服务器端口 */ 'port' => 8848, /** nacos 服务器用户名 */ 'username' => 'nacos', /** nacos服务器密码 */ 'password' => 'nacos', ];
而项目从nacos服务读取的配置会保存到项目根目录/config.yaml文件。文件内容如下,仅作为实例:
mysql: host: 127.0.0.1 port: '3306' username: root password: root
你的项目其他的配置文件可以通过读取yaml配置, 例如config/database.php,文件内容如下:
<?php return [ /** 默认连接方式 */ 'default'=>'mysql', /** mysql配置 */ 'mysql'=>[ /** 是否提前连接MySQL */ 'preStart'=>false, /** mysql基本配置 */ 'host'=>'192.168.4.106', 'username'=>'root', 'passwd'=>'root', 'dbname'=>'go_gin_chat', 'port'=>'3306' ], 'mysql2'=>[ 'host'=>yaml('mysql.host'), 'port'=>yaml('mysql.port'), 'username'=>yaml('mysql.username'), 'passwd'=>yaml('mysql.password'), 'dbname'=>yaml('mysql.dbname','go_gin_chat'), ] //todo 其他类型请自己去实现 ];
当nacos上的配置发生变化后,会自动拉取最新的配置,并重启项目
你可以使用NacosConfigManager::sync()发布你的配置,该命令会把你的config.yaml的内容发布到nacos服务器上去。
你可能需要一键搭建nacos服务,仅供参考:
docker run --name nacos -e MODE=standalone --env NACOS_AUTH_ENABLE=true -p 8848:8848 31181:31181 -d nacos/nacos-server:1.3.1
nacos这种负责管理配置和服务,安全性要求很高,一般不会销毁和重建,故没有将nacos服务绑定到基础容器里面。
ws服务(websocket)
创建ws服务
php start.php make:ws Just
自动生成的ws服务类如下
<?php namespace Ws; use RuntimeException; use Root\Lib\WsSelectorService; use Root\Lib\WsEpollService; /** * @purpose ws服务 * @author administrator * @time 2023-09-28 10:47:59 * @note 这是一个websocket服务端示例 */ class Just extends WsEpollService { /** ws 监听ip */ public string $host= '0.0.0.0'; /** 监听端口 */ public int $port = 9501; public function __construct(){ //todo 编写可能需要的逻辑 } /** * 建立连接事件 * @param $socket * @return mixed|void */ public function onConnect($socket) { // TODO: Implement onConnect() method. $allClients = $this->getAllUser(); $clients = []; foreach ($allClients as $client){ $clients[]=$client->id; } $this->sendTo($socket,['type'=>'getAllClients','content'=>$clients,'from'=>'server','to'=>$this->getUserInfoBySocket($socket)->id]); } /** * 消息事件 * @param $socket * @param $message * @return mixed|void */ public function onMessage($socket, $message) { // TODO: Implement onMessage() method. /** 消息格式 */ # type:[ping,message,getAllClients],content:[string,array,json],to:[uid,all] $message = json_decode($message,true); /** 消息类型 */ $type = $message['type']??null; /** 消息体 */ $content = $message['content']??''; /** 接收人 */ $sendTo = $message['to']??'all'; /** 处理消息 */ switch ($type){ /** 心跳 */ case 'ping': $this->sendTo($socket,['type'=>'pong','content'=>'pong','from'=>'sever','to'=>$this->getUserInfoBySocket($socket)->id??0]); break; /** 消息 */ case 'message': if ($sendTo=='all'){ $this->sendToAll(['type'=>'message','content'=>$content,'to'=>'all','from'=>$this->getUserInfoBySocket($socket)->id??0]); }else{ $to = $this->getUserInfoByUid($sendTo); $from = $this->getUserInfoBySocket($socket); if ($to){ $this->sendTo($to->socket,['type'=>'message','content'=>$content,'to'=>$to->id??0,'from'=>$from->id??0]); }else{ $this->sendTo($socket,['type'=>'message','content'=>'send message fail,the client is off line !','to'=>$from->id??0,'from'=>'server']); } } break; /** 获取所有的客户端 */ case "getAllClients": $allClients = $this->getAllUser(); $clients = []; foreach ($allClients as $client){ $clients[]=$client->id; } $this->sendTo($socket,['type'=>'getAllClients','content'=>$clients,'from'=>'server','to'=>$this->getUserInfoBySocket($socket)->id]); break; default: /** 未识别的消息类型 */ $this->sendTo($socket,['type'=>'error','content'=>'wrong message type !','from'=>'server','to'=>$this->getUserInfoBySocket($socket)->id]); } } /** * 连接断开事件 * @param $socket * @return mixed|void */ public function onClose($socket) { // TODO: Implement onClose() method. } /** * 异常事件 * @param $socket * @param \Exception $exception * @return mixed|void */ public function onError($socket, \Exception $exception) { //var_dump($exception->getMessage()); $this->close($socket); } }
开启服务 config/ws.php
<?php return [ 'ws1'=>[ /** 是否开启 */ 'enable'=>false, /** 服务类 */ 'handler'=>\Ws\TestWs::class, /** 监听ip */ 'host'=>'0.0.0.0', /** 监听端口 */ 'port'=>'9502' ], 'ws2'=>[ 'enable'=>true, 'handler'=>\Ws\TestWs2::class, 'host'=>'0.0.0.0', 'port'=>'9504' ], 'ws3'=>[ 'enable'=>true, 'handler'=>\Ws\Just::class, 'host'=>'0.0.0.0', 'port'=>'9503' ], ];
为方便测试,可以仅开启某一个ws服务,
php songshu ws:start Ws.Just
或者
php start.php ws:start Ws.Just
需注意命名空间大小写。须严格匹配。
javaScript客户端测试代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>websocket服务演示</title> </head> <body> <center><h3>ws服务演示</h3></center> <p>本页面仅作为演示,请根据自己的业务需求调整逻辑页面展示效果。</p> <input type="text" name="content" id="say" placeholder="请输入消息内容"/> <input type="text" name="uuid" id="uuid" placeholder="请输入消息接收人UUID"/> <button onclick="send()">发送广播消息</button> <button onclick="sendToOne()">发送私聊消息</button> <button onclick="getUser()">刷新在线用户</button> <div style="border: black solid 1px;width: 300px"> <h3>用户列表区</h3> <div id="user"></div> </div> <div> <h3>消息内容区</h3> <div id='content'></div> </div> <script> var connection = null; var ping = null; /** 连接ws服务*/ window.onload = function () { console.log('页面加载完成了!连接ws服务器'); connect(); }; /** 连接ws */ function connect() { console.log("连接服务器") /** 连接服务器 */ connection = new WebSocket('ws://localhost:9503'); /** 设置回调事件 */ connection.onopen = onopen; connection.onerror = onerror; connection.onclose = onclose; connection.onmessage = onmessage; } /** 发送消息*/ function send() { var content = document.getElementById('say').value; let msg = { type: 'message', content: content, to: 'all' }; connection.send(JSON.stringify(msg)); } /** * 发送私聊信息 */ function sendToOne() { var content = document.getElementById('say').value; var uuid = document.getElementById('uuid').value; let msg = { type: 'message', content: content, to: uuid }; connection.send(JSON.stringify(msg)); } /** 连接成功 */ function onopen() { let msg = { type: "ping", }; connection.send(JSON.stringify(msg)); console.log("连接成功,发送数据") /** 发送心跳 */ ping = setInterval(function () { let msg = { type: "ping", }; connection.send(JSON.stringify(msg)); }, 10000); } /** 错误 */ function onerror(error) { console.log(error) } /** 连接断开了 */ function onclose() { /** 重连服务器 */ console.log("重新连接服务器") /** 清除心跳 */ clearInterval(ping) /** 3秒后重连 */ setTimeout(function () { connect(); }, 10000) } /** 接收到消息 */ function onmessage(e) { var data = JSON.parse(e.data); /** 获取的在线用户列表 */ if (data.type == 'getAllClients') { var string = ''; data.content.forEach(function (item, index) { string = string + "<p>" + item + "</p>" }) document.getElementById('user').innerHTML = string }else{ /** 将接收到的普通聊天消息追加到页面 */ var own = document.getElementById('content') var content = "<p>" + e.data + "</p>" own.innerHTML = content + own.innerHTML; } } /** * 获取在线用户 */ function getUser() { let msg = { type: 'getAllClients', }; connection.send(JSON.stringify(msg)); } </script> </body> </html>
php版本的websocket客户端
以下是客户端使用示例。
首次使用需要初始化,调用setUp()设置服务端ip和port。
回调函数onMessage()方法负责处理用户的业务逻辑。
start()方法是阻塞函数,负责监听服务端消息。
send()函数负责发送消息,可以在任意地方调用。
get()方法负责读取一条消息,可以在任意地方调用。
use Root\Lib\WsClient; /** 初始化 设置需要连接的ws服务器 */ WsClient::setUp('127.0.0.1',9503); /** 发送一条数据 */ WsClient::send(['type'=>'ping']); /** 读取一条数据 */ var_dump(WsClient::get()); /** 设置消息回调函数,负责处理接收到消息后逻辑,若不设置,则自动丢弃消息 */ WsClient::$onMessage = function ($message){ $message = json_decode($message,true); /** 消息类型 */ $type = $message['type']??null; /** 消息体 */ $content = $message['content']??''; /** 接收人 */ $sendTo = $message['to']??'all'; if ($sendTo=='all'){ var_dump("广播的消息",$content); }else{ var_dump("私聊给我的消息",$content); } }; /** 开启客户端监听 */ WsClient::start();
流媒体服务rtmp
流媒体服务已经独立成为一个单独的项目了,地址是 https://github.com/2723659854/rtmp_server 。 你可以单独安装流媒体服务,命令如下:
composer create-project xiaosongshu/rtmp_server
流媒体服务配置 config/rtmp.php
<?php return [ /** 是否开启直播服务,该配置仅后台守护进程模式有效 */ 'enable'=>true, /** rmtp 服务端口 守护进程模式和开发模式均有效 */ 'rtmp'=>1935, /** flv端口 守护进程模式和开发模式均有效 */ 'flv'=>18080 ];
开启流媒体服务
# 调试模式 php start.php rtmp start # 守护模式 php start.php rtmp start -d # 重启服务(调试模式) php start.php rtmp restart # 关闭服务 php start.php rtmp stop
如果config/rtmp.php里面配置了'enable'=>true,在守护模式下rtmp会跟随项目一起启动。
直播推流地址:rtmp://127.0.0.1:1935/a/b
rtmp 拉流地址:rtmp://127.0.0.1:1935/a/b
http-flv播放地址: http://127.0.0.1:18080/a/b.flv
ws-flv播放地址: ws://127.0.0.1:18080/a/b.flv
推流工具 :obs,ffmpeg
拉流工具 :vlc播放器,web拉流
本框架提供web拉流,详见示例:http://localhost:8000/video/play
播放页面如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"> <meta name="referrer" content="no-referrer"> <title>小松鼠直播间演示</title> <style type="text/css"> html, body {width:100%;height:100%;margin:auto;overflow: hidden;} body {display:flex;} #mse {flex:auto;} </style> <script type="text/javascript"> window.addEventListener('resize',function(){document.getElementById('mse').style.height=window.innerHeight+'px';}); </script> </head> <body> <div id="mse" ></div> <script src="/js/xgplayer/player.js" charset="utf-8"></script> <script src="/js/xgplayer/player-flv.js" charset="utf-8"></script> <script type="text/javascript"> let player = new window.FlvJsPlayer({ id: 'mse', isLive: true, playsinline: true, url: 'http://127.0.0.1:18080/a/b.flv', autoplay: true, height: window.innerHeight, width: window.innerWidth, volume: 0 }); </script> </body> </html>
Http客户端
支持 http/https协议
使用方法如下
use Root\Lib\HttpClient; /** 同步请求 请求百度 */ $response = (HttpClient::request('www.baidu.com', 'GET',['lesson_id'=>201])); var_dump($response->header()); /** 异步请求 请求百度 */ /** 发送异步请求 */ HttpClient::requestAsync('127.0.0.1:9501', 'GET', ['lesson_id' => 201], [], [], function (Request $message) { if ($message->rawBuffer()){ var_dump("成功回调方法有数据"); } }, function (\RuntimeException $exception) { var_dump($exception->getMessage()); });
注意:在使用http异步请求客户端的时候 ,不要在成功回调和失败回调函数中抛出任何异常,如果需要抛出异常,一定要手动捕获。因为
在回调里面抛出异常,是没有其他服务来接管这个异常的,可能会导致进程摆烂。虽然本系统已经做了容错进行兜底,但是还是强烈建议,如果
一定要抛出异常,请自行捕获并处理异常。
若该http客户端不满足你的需求,你可以使用第三方http客户端,比如Guzzle。或者使用curl函数自己构建请求。
发送邮件
开启smtp服务 并获取授权码
登录到你的邮箱,设置开启smtp服务。一般在邮箱的设置,账户,smtp里面。
<ol> <li>QQ邮箱:https://service.mail.qq.com/detail/0/75</li> <li>网易163邮箱:https://help.mail.163.com/faq.do?m=list&categoryID=90</li> <li>新浪邮箱:https://help.sina.com.cn/comquestiondetail/view/1566/</li> <li>其他...</li> </ol>
发送邮件
/** 发件人 你的邮箱地址 */ $user = 'xxxxxxx@qq.com'; /** 发件人授权码 在邮箱的设置,账户,smtp里面 */ $password = 'xxxxxxxx'; /** 邮箱服务器地址 */ $url = 'smtp.qq.com:25'; try { /** 实例化客户端 */ $client = new \Xiaosongshu\Mail\Client(); /** 配置服务器地址 ,发件人信息 */ $client->config($url, $user, $password); /** 发送邮件 语法:[收件人邮箱] ,邮件主题, 邮件正文,[附件] */ $res = $client->send( ['xxxx@qq.com'],'标题', '正文呢',[__DIR__.'/favicon.ico',__DIR__.'/favicon2.ico',]); print_r($res); } catch (Exception $exception) { print_r("发送邮件失败"); print_r($exception->getMessage()); }
也可以修改自定义命令文件app/command/Email.php 文件的配置,测试发送邮件。
php start.php check:email
windows环境支持
如果需要在windows正式环境上线运行项目,执行php windows.php,如果需要关闭服务php windows.php stop即可。
命令行工具
创建自定义命令行: php start.php make:command Test
创建控制器: php start.php make:controller a/b/c
创建mysql模型: php start.php make:model index/user
创建sqlite模型: php start.php make:sqlite Demo
创建中间件: php start.php make:middleware Auth
创建redis消费者:php start.php make:queue Demo
创建rabbitmq消费者:php start.php make:rabbitmq DemoConsumer
项目打包:php -d phar.readonly=0 songshu make:phar
项目打包部署
为了方便部署服务,可以将整个项目打包上传到服务器,不再需要安装其它扩展,我们提供了一键 打包服务。项目将会被打包成phar格式文件,这个需要修改你的php.ini配置phar.readonly = Off, 当然了,如果觉得麻烦,那就直接在命令当中设置临时的phar.readonly = Off也是可以的。 打包命令:
php -d phar.readonly=0 songshu make:phar
打包后的项目,服务管理和原来一样的,只是将start.php或者songshu 换成了 songshu.phar 文件即可,在songshu.phar所在目录执行
php -d phar.readonly=0 songshu.phar start/restart/stop [-d]
编译二进制文件
仅支持linux运行环境
项目可以在任意平台打包编译,但是打包编译后的二进制文件仅支持linux环境,不兼容windows,mac。你也可以下载对应平台的PHP静态文件生成windows或者mac的可执行文件。
有可能你不想安装php环境,想直接运行项目,那么我们也提供了一键打包成二进制文件的方法,打包命令
php -d phar.readonly=0 songshu make:bin
打包完成后直接上传至服务器,进入到项目根目录,执行命令管理服务
./songshu.bin start/stop/restart [-d]
ps:不论是打包成phar文件,还是打包成bin文件,都需要在打包之前调整好生产环境的数据库,缓存等需要用到的服务配置。而且如果需要更新 配置文件,那么需要你重新打包。另外记得给项目根目录分配读写权限。理论上来说,这个不属于编译,只是把php文件和项目进行了拼接。
项目打包成exe (windows)
本项目可以打包编译成安装包,方便分发给其他用户安装使用。需要使用到第三方的服务实现,这已经超过了本项目的范围,有兴趣的coder可以自行操作。
日志
系统默认只记录运行的错误日志,按日记录,存放位置在 runtime/log/Y-m-d.log。提供记录日志函数dump_error(Exception|RuntimeException $exception), 若不满足需求,可以自己编写一个日志记录类。
其他
1,现在的网站都已经发展到前后端分离了,默认是无状态请求,cookie几乎没有用了。
所以没有编写cookie和session操作类了。 你可以使用token来识别用户,而不是cookie或者session。
2, 如果你在项目的根目录创建了自定义的目录,那么建议你使用require_once 方法手动
加载这些文件,当然你也可以使用composer的自动加载配置,编辑项目根目录的composer.json 文件,编辑字段psr4规范,
里面添加你需要加载的目录的名称,当然了,你的自定义目录必须符合Psr4规范,编辑完成后保存composer.json文件。最后执行
composer dump-autoload -o 命令,让composer刷新文件和对象的映射关系。做完以上的操作过后,你就可以使用use引入
你需要使用的类了。
补充
这种,在name两边加上英文波浪线,就是中横线
name
算法
算法部分都放在math.php
文件里面,可以在根目录执行php math.php
命令运行算法。有兴趣的朋友可以看一下。
动画
php在cli模式下,使用纯php开发动画,动画名称为《雪飘人间》,你可以在项目根目录下执行命令php snow.php
。你可以按ctrl + c
关闭动画。
本项目下的动画有:
php snow.php # 雪飘人间 这个是模拟的飘雪的效果,有闪电、云朵和雪花 php flower.php # 万花筒 别被名字唬住了,就是简单的螺旋形流星效果 php three.php # 旋转的立方体 这个和上面的动画不一样,是三维动画,将三维立方体投射到二维平面,生成动画
动画已经单独集成为插件了,安装命令如下:
composer require xiaosongshu/animation
下载地址: https://github.com/2723659854/animation
待办事项
1,支持多线程,在不使用swoole的情况下,使用多线程
2,支持在同一个进程内,可以将任务分解成多个子任务,每一个子任务独占一个线程,多个任务同时进行。