This commit is contained in:
mkm 2023-09-12 20:37:00 +08:00
commit a7683eaf40
75 changed files with 5336 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/.idea
/.vscode
/vendor
*.log
.env
/tests/tmp
/tests/.phpunit.result.cache

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

59
README.md Normal file
View File

@ -0,0 +1,59 @@
<div style="padding:18px;max-width: 1024px;margin:0 auto;background-color:#fff;color:#333">
<h1>webman</h1>
基于<a href="https://www.workerman.net" target="__blank">workerman</a>开发的超高性能PHP框架
<h1>学习</h1>
<ul>
<li>
<a href="https://www.workerman.net/webman" target="__blank">主页 / Home page</a>
</li>
<li>
<a href="https://www.workerman.net/doc/webman" target="__blank">文档 / Document</a>
</li>
<li>
<a href="https://www.workerman.net/doc/webman/install.html" target="__blank">安装 / Install</a>
</li>
<li>
<a href="https://www.workerman.net/questions" target="__blank">问答 / Questions</a>
</li>
<li>
<a href="https://www.workerman.net/apps" target="__blank">市场 / Apps</a>
</li>
<li>
<a href="https://www.workerman.net/sponsor" target="__blank">赞助 / Sponsors</a>
</li>
<li>
<a href="https://www.workerman.net/doc/webman/thanks.html" target="__blank">致谢 / Thanks</a>
</li>
</ul>
<div style="float:left;padding-bottom:30px;">
<h1>赞助商</h1>
<h4>特别赞助</h4>
<a href="https://www.crmeb.com/?form=workerman" target="__blank">
<img src="https://www.workerman.net/img/sponsors/6429/20230719111500.svg" width="200">
</a>
<h4>铂金赞助</h4>
<a href="https://www.fadetask.com/?from=workerman" target="__blank"><img src="https://www.workerman.net/img/sponsors/1/20230719084316.png" width="200"></a>
<a href="https://www.yilianyun.net/?from=workerman" target="__blank" style="margin-left:20px;"><img src="https://www.workerman.net/img/sponsors/6218/20230720114049.png" width="200"></a>
<h4>金牌赞助</h4>
</div>
<div style="clear: both">
<h1>LICENSE</h1>
The webman is open-sourced software licensed under the MIT.
</div>
</div>

123
app/Pusher.php Normal file
View File

@ -0,0 +1,123 @@
<?php
namespace app;
use Workerman\Connection\TcpConnection;
use IFlytek\Xfyun\Speech\ChatClient;
use WebSocket\Client;
use Workerman\Connection\AsyncTcpConnection;
class Pusher
{
private $app_id = '2eda6c2e';
private $api_key = '12ec1f9d113932575fc4b114a2f60ffd';
private $api_secret = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2';
public function onConnect(TcpConnection $connection)
{
echo "onConnect\n";
}
public function onWebSocketConnect(TcpConnection $connection, $http_buffer)
{
echo "onWebSocketConnect\n";
}
public function onMessage(TcpConnection $connection, $data)
{
if ($data != '') {
$uri = $this->assembleAuthUrl('ws://spark-api.xf-yun.com/v2.1/chat');
$con = new AsyncTcpConnection($uri);
$con->onWebSocketConnect = function (AsyncTcpConnection $con,$data) {
$res = $this->getBody($this->app_id,$data);
$con->send($res);
};
// $con->onMessage = function (TcpConnection $connection, $res) {
// // var_dump($res);
// // $con->send($res);
// $connection->send($res);
// };
$con->connect();
}
}
public function onClose(TcpConnection $connection)
{
echo "onClose\n";
}
function getBody($appid, $question)
{
$header = array(
"app_id" => $appid,
"uid" => "1"
);
$parameter = array(
"chat" => array(
"domain" => "generalv2",
"temperature" => 0.5,
"max_tokens" => 1024
)
);
$payload = array(
"message" => array(
"text" => array(
array("role" => "user", "content" => $question)
)
)
);
$json_string = json_encode(array(
"header" => $header,
"parameter" => $parameter,
"payload" => $payload
));
return $json_string;
}
function assembleAuthUrl($addr, $method = 'GET')
{
$apiKey = $this->api_key;
$apiSecret = $this->api_secret;
if ($apiKey == "" && $apiSecret == "") { // 不鉴权
return $addr;
}
$ul = parse_url($addr); // 解析地址
if ($ul === false) { // 地址不对,也不鉴权
return $addr;
}
// // $date = date(DATE_RFC1123); // 获取当前时间并格式化为RFC1123格式的字符串
$timestamp = time();
$rfc1123_format = gmdate("D, d M Y H:i:s \G\M\T", $timestamp);
// $rfc1123_format = "Mon, 31 Jul 2023 08:24:03 GMT";
// 参与签名的字段 host, date, request-line
$signString = array("host: " . $ul["host"], "date: " . $rfc1123_format, $method . " " . $ul["path"] . " HTTP/1.1");
// 对签名字符串进行排序,确保顺序一致
// ksort($signString);
// 将签名字符串拼接成一个字符串
$sgin = implode("\n", $signString);
// 对签名字符串进行HMAC-SHA256加密得到签名结果
$sha = hash_hmac('sha256', $sgin, $apiSecret, true);
$signature_sha_base64 = base64_encode($sha);
// 将API密钥、算法、头部信息和签名结果拼接成一个授权URL
$authUrl = "api_key=\"$apiKey\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"$signature_sha_base64\"";
// 对授权URL进行Base64编码并添加到原始地址后面作为查询参数
$authAddr = $addr . '?' . http_build_query(array(
'host' => $ul['host'],
'date' => $rfc1123_format,
'authorization' => base64_encode($authUrl),
));
return $authAddr;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace app\controller;
use support\Request;
use Workerman\Protocols\Websocket;
class IndexController
{
public function index(Request $request)
{
static $readme;
if (!$readme) {
$readme = file_get_contents(base_path('README.md'));
}
return $readme;
}
public function view(Request $request)
{
return view('index/view', ['name' => 'webman']);
}
public function json(Request $request)
{
return json(['code' => 0, 'msg' => 'ok']);
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Config;
/**
* 配置类接口
*
* @author guizheng@iflytek.com
*/
interface ConfigInterface
{
/**
* 返回配置内容的json形式
*
* @return string
*/
public function toJson();
/**
* 返回配置内容的数组形式
*
* @return array
*/
public function toArray();
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Handler;
use GuzzleHttp\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* GuzzleHttp-7处理类
*
* @author guizheng@iflytek.com
*/
class Guzzle7HttpHandler{
/**
* @var ClientInterface
*/
private $client;
/**
* @param ClientInterface $client
*/
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
/**
* 接收Psr-7的request对象和请求参数返回Psr-7的response对象
*
* @param RequestInterface $request
* @param array $options
* @return ResponseInterface
*/
public function __invoke(RequestInterface $request, array $options = [])
{
return $this->client->send($request, $options);
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Handler;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
/**
* HttpHandler工厂类
*
* @author guizheng@iflytek.com
*/
class HttpHandlerFactory
{
/**
* 根据安装的GuzzleHttp版本获取默认的处理类
*
* @param ClientInterface $client
* @return Guzzle6HttpHandler|Guzzle7HttpHandler
* @throws \Exception
*/
public static function build(ClientInterface $client = null)
{
$client = $client ?: new Client();
return new Guzzle7HttpHandler($client);
}
}

View File

@ -0,0 +1,126 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Handler;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
use WebSocket\Client;
use WebSocket\Exception;
use GuzzleHttp\Psr7\Response;
use Psr\Log\{LoggerAwareInterface, LoggerInterface, NullLogger};
/**
* WebSocket处理类
*
* @author guizheng@iflytek.com
*/
class WsHandler implements LoggerAwareInterface
{
use JsonTrait;
/**
* @var WebSocket\Client ws client
*/
private $client;
/**
* @var string 发送的字符串
*/
private $input;
/**
* @var LoggerInterface or null 日志处理
*/
private $logger;
public function __construct($uri, $input, $timeout = 300, $logger = null)
{
$this->client = new Client($uri);
$this->client->setTimeout($timeout);
$this->input = $input;
$this->logger = $logger ?: new NullLogger();
}
/**
* 发送并等待获取返回
*
* 这是一个同步阻塞的过程,调用将持续到结果返回或者出现超时
*/
public function sendAndReceive()
{
$result = '';
try {
$this->logger->info("Start to send data, input: {$this->input}");
$this->client->send($this->input);
$printSid = true;
while (true) {
$message = $this->jsonDecode($this->client->receive());
if ($message->code !== 0) {
throw new \Exception(json_encode($message));
}
if ($printSid) {
$this->logger->info("Receiving data, sid-[{$message->sid}]");
$printSid = false;
}
switch ($message->data->status) {
case 1:
$result .= base64_decode($message->data->audio);
break;
case 2:
$result .= base64_decode($message->data->audio);
break 2;
}
}
$this->logger->info("Receive data successfully, total length: " . strlen($result));
return new Response(200, [], $result);
} catch (Exception $ex) {
throw $ex;
}
}
public function send($message = null)
{
try {
if (empty($message)) {
if (!empty($this->input)) {
$message = $this->input;
} else {
throw new Exception();
}
}
return $this->client->send($message);
} catch (Exception $ex) {
throw $ex;
}
}
public function receive()
{
try {
return $this->client->receive();
} catch (Exception $ex) {
throw $ex;
}
}
public function setLogger(LoggerInterface $logger = null)
{
$this->logger = $logger ?: new NullLogger();
}
}

View File

@ -0,0 +1,163 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core;
/**
* Http客户端
*
* @author guizheng@iflytek.com
*/
use Exception;
use GuzzleHttp\Psr7\Response;
use IFlytek\Xfyun\Core\Handler\Guzzle7HttpHandler;
use IFlytek\Xfyun\Core\Traits\SignTrait;
use IFlytek\Xfyun\Core\Traits\DecideRetryTrait;
use IFlytek\Xfyun\Core\Handler\HttpHandlerFactory;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Psr7\Utils;
class HttpClient
{
use SignTrait;
use DecideRetryTrait;
const MAX_DELAY_MICROSECONDS = 60000000;
/**
* @var Guzzle7HttpHandler
*/
private $httpHandler;
/**
* @var array 要添加的头部信息
*/
private $httpHeaders;
/**
* @var int 超时时间
*/
private $requestTimeout;
/**
* @var int 重试次数
*/
private $retries;
/**
* @var int 重试次数
*/
private $decideRetryFunction;
/**
* @var int 重试次数
*/
private $delayFunction;
/**
* @var int 重试次数
*/
private $calcDelayFunction;
public function __construct($config)
{
$config += [
'httpHandler' => null,
'httpHeaders' => [],
'requestTimeout' => 3000,
'retries' => 3,
'decideRetryFunction' => null,
'delayFunction' => null,
'calcDelayFunction' => null
];
$this->httpHandler = $config['httpHandler'] ?: HttpHandlerFactory::build();
$this->httpHeaders = $config['httpHeaders'];
$this->retries = $config['retries'];
$this->decideRetryFunction = $config['decideRetryFunction'] ?: $this->getDecideRetryFunction();
$this->calcDelayFunction = $config['calcDelayFunction'] ?: [$this, 'calculateDelay'];
$this->delayFunction = $config['delayFunction'] ?: static function ($delay) {
usleep($delay);
};
}
/**
* 发送请求,并接受返回
*
* @param RequestInterface $request
* @param array $options 请求的配置参数
* @return Response
* @throws Exception
*/
public function sendAndReceive(RequestInterface $request, array $options = [])
{
try {
$delayFunction = $this->delayFunction;
$calcDelayFunction = $this->calcDelayFunction;
$retryAttempt = 0;
while (true) {
try {
return call_user_func_array($this->httpHandler, [$this->applyHeaders($request), $options]);
} catch (Exception $exception) {
if ($this->decideRetryFunction) {
if (!call_user_func($this->decideRetryFunction, $exception)) {
throw $exception;
}
}
if ($retryAttempt >= $this->retries) {
break;
}
$delayFunction($calcDelayFunction($retryAttempt));
$retryAttempt++;
}
}
throw $exception;
} catch (Exception $ex) {
throw $ex;
}
}
/**
* 将头部信息添加到请求对象中
*
* @param $request 请求对象
* @return RequestInterface
*/
private function applyHeaders($request)
{
return Utils::modifyRequest($request, ['set_headers' => $this->httpHeaders]);
}
/**
* 根据重试次数计算下次重试延迟时间
*
* @param int $attempt 重试次数
* @return int
*/
public static function calculateDelay($attempt)
{
return min(
mt_rand(0, 1000000) + (pow(2, $attempt) * 1000000),
self::MAX_DELAY_MICROSECONDS
);
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Traits;
/**
* 数组处理
*
* @author guizheng@iflytek.com
*/
trait ArrayTrait
{
/**
* 递归移除数组中的null项
*
* @param array $array
* @return array
*/
private static function removeNull($array)
{
foreach ($array as $key => $item) {
if (is_array($item)) {
$array[$key] = self::removeNull($item);
} else if (is_null($item)) {
unset($array[$key]);
}
}
return $array;
}
}

View File

@ -0,0 +1,96 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Traits;
use GuzzleHttp\Exception\RequestException;
/**
* 提供一个方法,决定是否进行重试
*
* @author guizheng@iflytek.com
*/
trait DecideRetryTrait
{
use JsonTrait;
/**
* @var array
*/
private $httpRetryCodes = [
500,
502,
503
];
/**
* @var array
*/
private $httpRetryMessages = [
'retry later'
];
/**
* 返回一个callable变量作用是决定是否重试
*
* @param bool $shouldRetryMessages 是否要根据message决定重试与否
* @return callable
*/
private function getDecideRetryFunction($shouldRetryMessages = true)
{
$httpRetryCodes = $this->httpRetryCodes;
$httpRetryMessages = $this->httpRetryMessages;
return function (\Exception $ex) use ($httpRetryCodes, $httpRetryMessages, $shouldRetryMessages) {
$statusCode = $ex->getCode();
if (in_array($statusCode, $httpRetryCodes)) {
return true;
}
if (!$shouldRetryMessages) {
return false;
}
$message = ($ex instanceof RequestException && $ex->hasResponse())
? (string) $ex->getResponse()->getBody()
: $ex->getMessage();
try {
$message = $this->jsonDecode(
$message,
true
);
} catch (\InvalidArgumentException $ex) {
return false;
}
if (!isset($message['errors'])) {
return false;
}
foreach ($message['errors'] as $error) {
if (in_array($error['reason'], $httpRetryMessages)) {
return true;
}
}
return false;
};
}
}

View File

@ -0,0 +1,68 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Iflytek\Xfyun\Core\Traits;
/**
* 提供对原生json_encode和json_decode的封装以便在发生错误时抛出一个异常
*
* @author guizheng@iflytek.com
*/
trait JsonTrait
{
/**
* @param string $json 待解码的字符串
* @param bool $assoc 是否返回数组
* @param int $depth 递归深度
* @param int $options json_decode的配置
* @return mixed
* @throws \InvalidArgumentException
*/
private static function jsonDecode($json, $assoc = false, $options = 0, $depth = 512)
{
$data = json_decode($json, $assoc, $depth, $options);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_decode error: ' . json_last_error_msg()
);
}
return $data;
}
/**
* @param mixed $value 待编码的变量
* @param int $options json_decode的配置
* @param int $depth 递归深度
* @return string
* @throws \InvalidArgumentException
*/
private static function jsonEncode($value, $options = 0, $depth = 512)
{
$json = json_encode($value, $options, $depth);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_encode error: ' . json_last_error_msg()
);
}
return $json;
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core\Traits;
/**
* 提供平台的签名方法
*
* @author guizheng@iflytek.com
*/
trait SignTrait
{
/**
* 根据secret对uri进行签名返回签名后的uri
*
* @param string $uri 待签名的uri
* @param array $secret 秘钥信息
* @return string
*/
private static function signUriV1($uri, $secret)
{
$apiKey = $secret['apiKey'];
$apiSecret = $secret['apiSecret'];
$host = $secret['host'];
$request_line = $secret['requestLine'];
$date = empty($secret['date']) ? gmdate ('D, d M Y H:i:s \G\M\T', time()) : $secret['date'];
$signature_origin = "host: $host\ndate: $date\n$request_line";
$signature_sha = hash_hmac('sha256', $signature_origin, $apiSecret, true);
$signature = base64_encode($signature_sha);
$authrization = base64_encode("api_key=\"$apiKey\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"$signature\"");
$uri = $uri . '?' . http_build_query([
'host' => $host,
'date' => $date,
'authorization' => $authrization
]);
return $uri;
}
/**
* 根据所提供信息返回签名
*
* @param string $appId appid
* @param string $secretKey secretKey
* @param string $timestamp 时间戳,不传的话使用系统时间
* @return string
*/
public static function signV1($appId, $secretKey, $timestamp = null)
{
$timestamp = $timestamp ?: time();
$baseString = $appId . $timestamp;
$signa_origin = hash_hmac('sha1', md5($baseString), $secretKey, true);
return base64_encode($signa_origin);
}
/**
* https调用的鉴权参数构造
*
* @param string $appId appId
* @param string $apiKey apiKey
* @param string $curTime curTime
* @param string $param param
* @return array
*/
public static function signV2($appId, $apiKey, $param, $curTime = null)
{
if (empty($curTime)) {
$curTime = time();
}
return [
'X-Appid' => $appId,
'X-CurTime' => $curTime,
'X-Param' => base64_encode($param),
'X-CheckSum' => md5($apiKey . $curTime . base64_encode($param))
];
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Core;
/**
* WebSocket客户端
*
* @author guizheng@iflytek.com
*/
class WsClient
{
/** @var WsHandler */
private $handler;
public function __construct($config)
{
$config += [
'handler' => null,
];
$this->handler = $config['handler'];
}
/**
* 发送请求,并接受返回
*
* @return Response
*/
public function sendAndReceive()
{
try {
return call_user_func_array([$this->handler, 'sendAndReceive'], []);
} catch (\Exception $ex) {
throw $ex;
}
}
public function send($message = null)
{
try {
return call_user_func_array([$this->handler, 'send'], [$message]);
} catch (\Exception $ex) {
throw $ex;
}
}
public function receive()
{
try {
return call_user_func_array([$this->handler, 'receive'], []);
} catch (\Exception $ex) {
throw $ex;
}
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* Copyright 1999-2022 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace extend\IFlytek\Xfyun\Speech;
/**
* chat户端
*
* @author guizheng@iflytek.com
*/
class ChatClient
{
protected $appId;
protected $apiKey;
protected $apiSecret;
protected $uid;
protected $resId;
protected $requestConfig;
protected $client;
public function __construct($appId, $apiKey, $apiSecret, $uid = null, $resId = null, $requestConfig = [])
{
$this->appId = $appId;
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->uid = $uid;
$this->resId = $resId;
}
function assembleAuthUrl($addr,$method='GET') {
$apiKey=$this->apiKey;
$apiSecret=$this->apiSecret;
if ($apiKey == "" && $apiSecret == "") { // 不鉴权
return $addr;
}
$ul = parse_url($addr); // 解析地址
if ($ul === false) { // 地址不对,也不鉴权
return $addr;
}
// // $date = date(DATE_RFC1123); // 获取当前时间并格式化为RFC1123格式的字符串
$timestamp = time();
$rfc1123_format = gmdate("D, d M Y H:i:s \G\M\T", $timestamp);
// $rfc1123_format = "Mon, 31 Jul 2023 08:24:03 GMT";
// 参与签名的字段 host, date, request-line
$signString = array("host: " . $ul["host"], "date: " . $rfc1123_format, $method . " " . $ul["path"] . " HTTP/1.1");
// 对签名字符串进行排序,确保顺序一致
// ksort($signString);
// 将签名字符串拼接成一个字符串
$sgin = implode("\n", $signString);
// 对签名字符串进行HMAC-SHA256加密得到签名结果
$sha = hash_hmac('sha256', $sgin, $apiSecret,true);
$signature_sha_base64 = base64_encode($sha);
// 将API密钥、算法、头部信息和签名结果拼接成一个授权URL
$authUrl = "api_key=\"$apiKey\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"$signature_sha_base64\"";
// 对授权URL进行Base64编码并添加到原始地址后面作为查询参数
$authAddr = $addr . '?' . http_build_query(array(
'host' => $ul['host'],
'date' => $rfc1123_format,
'authorization' => base64_encode($authUrl),
));
return $authAddr;
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Config;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
use IFlytek\Xfyun\Core\Config\ConfigInterface;
/**
* 性别年龄识别配置参数类
*
* @author guizheng@iflytek.com
*/
class IgrConfig implements ConfigInterface
{
use ArrayTrait;
use JsonTrait;
/**
* @var string 引擎类型
* 仅支持igr
*/
private $ent;
/**
* @var string 音频格式
* raw原生音频数据pcm格式
* speexspeex格式rate需设置为8000
* speex-wb宽频speex格式rate需设置为16000
* amramr格式rate需设置为8000
* amr-wb宽频amr格式rate需设置为16000
* 默认raw
*/
private $aue;
/**
* @var int 音频采样率
* 16000/8000
* 默认16000
*/
private $rate;
public function __construct($config)
{
$config += [
'aue' => 'raw',
'rate' => 16000
];
$this->ent = 'igr';
$this->aue = $config['aue'];
$this->rate = $config['rate'];
}
/**
* 去除null项后返回数组形式
*
* @return array
*/
public function toArray()
{
return $this->removeNull([
'ent' => $this->ent,
'aue' => $this->aue,
'rate' => $this->rate
]);
}
/**
* 返回toArray的Json格式
*
* @return string
*/
public function toJson()
{
return $this->jsonEncode($this->toArray());
}
/**
* @return string
*/
public function getAue()
{
return $this->aue;
}
/**
* @return int
*/
public function getRate()
{
return $this->rate;
}
}

View File

@ -0,0 +1,265 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Config;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
use IFlytek\Xfyun\Core\Config\ConfigInterface;
/**
* 语音评测配置参数类
*
* @author guizheng@iflytek.com
*/
class IseConfig implements ConfigInterface
{
use ArrayTrait;
use JsonTrait;
/**
* @var string 服务类型指定
* ise: 开放评测
* 默认'ise'
*/
private $sub;
/**
* @var string 评测语种
* 中文cn_vip英文en_vip
* 默认'cn_vip'
*/
private $ent;
/**
* @var string 评测题型
* 中文题型:
* read_syllable单字朗读汉语专有
* read_word词语朗读
* read_sentence句子朗读
* read_chapter(篇章朗读)
* 英文题型:
* read_word词语朗读
* read_sentence句子朗读
* read_chapter(篇章朗读)
* simple_expression英文情景反应
* read_choice英文选择题
* topic英文自由题
* retell英文复述题
* picture_talk英文看图说话
* oral_translation英文口头翻译
* 默认read_sentence
*/
private $category;
/**
* @var int 上传音频时来区分音频的状态
* 1/2/4
* 默认1
*/
private $aus;
/**
* @var string 用来区分数据上传阶段
* ssb参数上传阶段
* ttp文本上传阶段ttp_skip=true时该阶段可以跳过直接使用text字段中的文本
* auw音频上传阶段
* 必传
*/
private $cmd;
/**
* @var string 待评测文本
* 需要加utf8bom头'\uFEFF' + text
*/
private $text;
/**
* @var string 带评测文本编码
* utf-8/gbk
* 默认utf-8
*/
private $tte;
/**
* @var boolean 跳过ttp直接使用text中的文本进行评测
* true/false
* 默认true
*/
private $ttpSkip;
/**
* @var string 拓展能力生效条件ise_unite="1", rst="entirety"
* 多维度分信息显示准确度分、流畅度分、完整度打分extra_ability值为multi_dimension字词句篇均适用如选多个能力用分号隔开。例如extra_ability=syll_phone_err_msg;pitch;multi_dimension
* 单词基频信息显示基频开始值、结束值extra_ability值为pitch ,仅适用于单词和句子题型
* 音素错误信息显示声韵、调型是否正确extra_ability值为syll_phone_err_msg字词句篇均适用,如选多个能力用分号隔开。例如extra_ability=syll_phone_err_msg;pitch;multi_dimension
*/
private $extraAbility;
/**
* @var string 音频格式
* raw: 未压缩的pcm格式音频或wav如果用wav格式音频建议去掉头部
* lame: mp3格式音频
* speex-wb;7: 讯飞定制speex格式音频
* 默认speex-wb
*/
private $aue;
/**
* @var string 音频采样率
* 默认 audio/L16;rate=16000
*/
private $auf;
/**
* @var string 返回结果格式
* utf8/gbk
* 默认utf8
*/
private $rstcd;
/**
* @var string 评测人群指定
* adult成人群体不设置群体参数时默认为成人
* youth中学群体
* pupil小学群体中文句、篇题型设置此参数值会有accuracy_score得分的返回
* 默认adult
*/
private $group;
/**
* @var string 设置评测的打分及检错松严门限(仅中文引擎支持)
* easy容易
* common普通
* hard困难
*/
private $checkType;
/**
* @var string 设置评测的学段参数 (仅中文题型:中小学的句子、篇章题型支持)
* junior(1,2年级)
* middle(3,4年级)
* senior(5,6年级)
*/
private $grade;
/**
* @var string 评测返回结果与分制控制评测返回结果与分制控制也会受到ise_unite与plev参数的影响
* 完整entirety默认值
* 中文百分制推荐传参rst="entirety"且ise_unite="1"且配合extra_ability参数使用
* 英文百分制推荐传参rst="entirety"且ise_unite="1"且配合extra_ability参数使用
* 精简plain评测返回结果将只有总分
*/
private $rst;
/**
* @var string 返回结果控制
* 0:不控制(默认值)
* 1控制extra_ability参数将影响全维度等信息的返回
*/
private $iseUnite;
/**
* @var string 在rst="entirety"默认值且ise_unite="0"默认值的情况下plev的取值不同对返回结果有影响。
* plev0(给出全部信息汉语包含rec_node_type、perr_msg、fluency_score、phone_score信息的返回英文包含accuracy_score、serr_msg、 syll_accent、fluency_score、standard_score、pitch信息的返回)
*/
private $plev;
public function __construct($config)
{
$config += [
'ent' => 'cn_vip',
'category' => 'read_sentence',
'aus' => 1,
'cmd' => 'ssb',
'text' => '',
'tte' => 'utf-8',
'ttp_skip' => true,
'extra_ability' => null,
'aue' => 'raw',
'rstcd' => 'utf8',
'group' => 'adult',
'check_type' => 'common',
'grade' => 'middle',
'rst' => 'entirety',
'ise_unite' => '0',
'plev' => '0'
];
$this->sub = 'ise';
$this->ent = $config['ent'];
$this->category = $config['category'];
$this->aus = $config['aus'];
$this->cmd = $config['cmd'];
$this->text = chr(239) . chr(187) . chr(191) . $config['text'];
$this->tte = $config['tte'];
$this->ttpSkip = $config['ttp_skip'];
$this->extraAbility = $config['extra_ability'];
$this->aue = $config['aue'];
$this->rstcd = $config['rstcd'];
$this->group = $config['group'];
$this->checkType = $config['check_type'];
$this->grade = $config['grade'];
$this->rst = $config['rst'];
$this->iseUnite = $config['ise_unite'];
$this->plev = $config['plev'];
}
/**
* 去除null项后返回数组形式
*
* @return array
*/
public function toArray()
{
return $this->removeNull([
'sub' => $this->sub,
'ent' => $this->ent,
'category' => $this->category,
'aus' => $this->aus,
'cmd' => $this->cmd,
'text' => $this->text,
'tte' => $this->tte,
'ttp_skip' => $this->ttpSkip,
'extra_ability' => $this->extraAbility,
'aue' => $this->aue,
'rstcd' => $this->rstcd,
'group' => $this->group,
'check_type' => $this->checkType,
'grade' => $this->grade,
'rst' => $this->rst,
'ise_unite' => $this->iseUnite,
'plev' => $this->plev
]);
}
/**
* 返回toArray的Json格式
*
* @return string
*/
public function toJson()
{
return $this->jsonEncode($this->toArray());
}
public function setText($text)
{
$this->text = chr(239) . chr(187) . chr(191) . $text;
}
}

View File

@ -0,0 +1,142 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Config;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
use IFlytek\Xfyun\Core\Config\ConfigInterface;
/**
* 语音转写配置参数类
*
* @author guizheng@iflytek.com
*/
class LfasrConfig implements ConfigInterface
{
use ArrayTrait;
use JsonTrait;
/**
* @var string 转写类型
* 0: (标准版,格式: wav,flac,opus,mp3,m4a)
* 默认: 0
*/
private $lfasrType;
/**
* @var string 转写结果是否包含分词信息
* false或true
* 默认false
*/
private $hasParticiple;
/**
* @var string 转写结果中最大的候选词个数
* 0-5
* 默认0
*/
private $maxAlternatives;
/**
* @var string 发音人个数
* 0-10
* 默认2
*/
private $speakerNumber;
/**
* @var string 是否包含发音人分离信息
* false或true
* 默认false
*/
private $hasSeperate;
/**
* @var string 角色分离类型
* 1: 通用角色分离2: 电话信道角色分离适用于speaker_number为2的说话场景
* 默认1该字段只有在开通了角色分离功能的前提下才会生效正确传入该参数后角色分离效果会有所提升。
*/
private $roleType;
/**
* @var string 语种
* cn: 中英文&中文en: 英文(英文不支持热词)
* 默认cn
*/
private $language;
/**
* @var string 垂直领域个性化参数
* court: 法院; edu: 教育; finance: 金融; medical: 医疗; tech: 科技;
* 默认通用
*/
private $pd;
public function __construct($config)
{
$config += [
'lfasr_type' => '0',
'has_participle' => 'false',
'max_alternatives' => '0',
'speaker_number' => '2',
'has_seperate' => 'false',
'role_type' => '1',
'language' => 'cn',
'pd' => null
];
$this->lfasrType = $config['lfasr_type'];
$this->hasParticiple = $config['has_participle'];
$this->maxAlternatives = $config['max_alternatives'];
$this->speakerNumber = $config['speaker_number'];
$this->hasSeperate = $config['has_seperate'];
$this->roleType = $config['role_type'];
$this->language = $config['language'];
$this->pd = $config['pd'];
}
/**
* 去除null项后返回数组形式
*
* @return array
*/
public function toArray()
{
return $this->removeNull([
'lfasr_type' => $this->lfasrType,
'has_participle' => $this->hasParticiple,
'max_alternatives' => $this->maxAlternatives,
'speaker_number' => $this->speakerNumber,
'has_seperate' => $this->hasSeperate,
'role_type' => $this->roleType,
'language' => $this->language,
'pd' => $this->pd
]);
}
/**
* 返回toArray的Json格式
*
* @return string
*/
public function toJson()
{
return $this->jsonEncode($this->toArray());
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Config;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Config\ConfigInterface;
/**
* 文本纠错配置参数类
*
* @author guizheng@iflytek.com
*/
class TcConfig implements ConfigInterface
{
use ArrayTrait;
private $resultEncoding;
private $resultCompress;
private $resultFormat;
private $inputEncoding;
private $inputCompress;
private $inputFormat;
public function __construct($config)
{
$config += [
'resultEncoding' => 'utf8',
'resultCompress' => 'raw',
'resultFormat' => 'json',
'inputEncoding' => 'utf8',
'inputCompress' => 'raw',
'inputFormat' => 'json'
];
$this->resultEncoding = $config['resultEncoding'];
$this->resultCompress = $config['resultCompress'];
$this->resultFormat = $config['resultFormat'];
$this->inputEncoding = $config['inputEncoding'];
$this->inputCompress = $config['inputCompress'];
$this->inputFormat = $config['inputFormat'];
}
public function toJson()
{
// TODO: Implement toJson() method.
}
public function toArray()
{
return self::removeNull([
'resultEncoding' => $this->resultEncoding,
'resultCompress' => $this->resultCompress,
'resultFormat' => $this->resultFormat,
'inputEncoding' => $this->inputEncoding,
'inputCompress' => $this->inputCompress,
'inputFormat' => $this->inputFormat
]);
}
}

View File

@ -0,0 +1,191 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Config;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
use IFlytek\Xfyun\Core\Config\ConfigInterface;
/**
* 语音合成配置参数类
*
* @author guizheng@iflytek.com
*/
class TtsConfig implements ConfigInterface
{
use ArrayTrait;
use JsonTrait;
/**
* @var string 音频编码(必填)
* raw:未压缩的pcm
* lame:mp3 (当aue=lame时需传参sfl=1)
* speex-org-wb;7: 标准开源speexfor speex_wideband即16k数字代表指定压缩等级默认等级为8
* speex-org-nb;7: 标准开源speexfor speex_narrowband即8k数字代表指定压缩等级默认等级为8
* speex;7:压缩格式压缩等级1~10默认为78k讯飞定制speex
* speex-wb;7:压缩格式压缩等级1~10默认为716k讯飞定制speex
* 本sdk将默认采用raw格式
*/
private $aue;
/**
* @var int 开启流式返回
* 需要配合aue=lame使用开启流式返回mp3格式音频取值:1
*/
private $sfl;
/**
* @var string 音频采样率
* audio/L16;rate=8000: 合成8K的音频
* audio/L16;rate=16000: 合成16K的音频
* auf不传值: 合成16K的音频
*/
private $auf;
/**
* @var string 发音人(必填)
* 可选值: 请到控制台添加试用或购买
* 默认xiaoyan
*/
private $vcn;
/**
* @var int 语速
* 可选值: 0-100
* 默认为50
*/
private $speed;
/**
* @var int 音量
* 可选值: 0-100
* 默认为50
*/
private $volume;
/**
* @var int 音高
* 可选值: 0-100
* 默认为50
*/
private $pitch;
/**
* @var int 合成音频的背景音
* 0: 无背景音; 1: 有背景音
* 默认0
*/
private $bgs;
/**
* @var string 文本编码格式
* GB2312|GBK|BIG5|UNICODE|GB18030|UTF8
* 默认UTF-8
*/
private $tte;
/**
* @var string 设置英文发音方式
* 0: 自动判断处理,如果不确定将按照英文词语拼写处理
* 1: 所有英文按字母发音
* 2: 自动判断处理,如果不确定将按照字母朗读
* 默认2
*/
private $reg;
/**
* @var string 合成音频数字发音方式
* 0: 自动判断
* 1: 完全数值
* 2: 完全字符串
* 3: 字符串优先
* 默认0
*/
private $rdn;
/**
* @var string 自定义发音人引擎
* 默认传 ptts
*/
private $ent;
public function __construct($config = [])
{
$config += [
'aue' => 'lame',
'sfl' => 1,
'auf' => null,
'vcn' => 'xiaoyan',
'speed' => 50,
'volume' => 50,
'pitch' => 50,
'bgs' => 0,
'tte' => 'UTF8',
'reg' => '2',
'rdn' => '0',
'ent' => ''
];
$this->aue = $config['aue'];
$this->sfl = $config['sfl'];
$this->auf = $config['auf'];
$this->vcn = $config['vcn'];
$this->speed = $config['speed'];
$this->volume = $config['volume'];
$this->pitch = $config['pitch'];
$this->bgs = $config['bgs'];
$this->tte = $config['tte'];
$this->reg = $config['reg'];
$this->rdn = $config['rdn'];
$this->ent = $config['ent'];
}
/**
* 去除null项后返回数组形式
*
* @return array
*/
public function toArray()
{
return $this->removeNull([
'aue' => $this->aue,
'sfl' => $this->sfl,
'auf' => $this->auf,
'vcn' => $this->vcn,
'speed' => $this->speed,
'volume' => $this->volume,
'pitch' => $this->pitch,
'bgs' => $this->bgs,
'tte' => $this->tte,
'reg' => $this->reg,
'rdn' => $this->rdn,
'ent' => $this->ent
]);
}
/**
* 返回toArray的Json格式
*
* @return string
*/
public function toJson()
{
return $this->jsonEncode($this->toArray());
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Constants;
/**
* 星火
*
* @author
*/
class ChatConstants
{
const URI = 'wss://spark-api.xf-yun.com/v2.1/chat';
const REQUEST_LINE = 'POST wss://spark-api.xf-yun.com/v2.1/chat HTTP/1.1';
}

View File

@ -0,0 +1,31 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Constants;
/**
* 性别年龄识别常量
*
* @author guizheng@iflytek.com
*/
class IgrConstants
{
const URI = 'wss://ws-api.xfyun.cn/v2/igr';
const HOST = 'ws-api.xfyun.cn';
const REQUEST_LINE = 'GET /v2/igr HTTP/1.1';
const FRAME_SIZE = 1280;
}

View File

@ -0,0 +1,31 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Constants;
/**
* 语音评测常量
*
* @author guizheng@iflytek.com
*/
class IseConstants
{
const URI = 'wss://ise-api.xfyun.cn/v2/open-ise';
const HOST = 'ise-api.xfyun.cn';
const REQUEST_LINE = 'GET /v2/open-ise HTTP/1.1';
const FRAME_SIZE = 1280;
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Constants;
/**
* 语音转写常量
*
* @author guizheng@iflytek.com
*/
class LfasrConstants
{
const URI_PREPARE = 'https://raasr.xfyun.cn/api/prepare';
const URI_UPLOAD = 'https://raasr.xfyun.cn/api/upload';
const URI_MERGE = 'https://raasr.xfyun.cn/api/merge';
const URI_GET_PROGRESS = 'https://raasr.xfyun.cn/api/getProgress';
const URI_GET_RESULT = 'https://raasr.xfyun.cn/api/getResult';
const SLICE_PIECE_SIZE = 1024 * 1024 * 10;
const ORIGIN_SLICE_ID = 'aaaaaaaaaa';
}

View File

@ -0,0 +1,31 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Constants;
/**
* 文本纠错常量
*
* @author guizheng@iflytek.com
*/
class TcConstants
{
const URI = 'https://api.xf-yun.com/v1/private/s9a87e3ec';
const LIST_UPLOAD_URI = 'https://evo-gen.xfyun.cn/individuation/gen/upload';
const HOST = 'api.xf-yun.com';
const REQUEST_LINE = 'POST /v1/private/s9a87e3ec HTTP/1.1';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Constants;
/**
* 语音合成常量
*
* @author guizheng@iflytek.com
*/
class TtsConstants
{
const URI = 'wss://tts-api.xfyun.cn/v2/tts';
const HOST = 'tts-api.xfyun.cn';
const REQUEST_LINE = 'GET /v2/tts HTTP/1.1';
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Helper;
use IFlytek\Xfyun\Speech\Constants\LfasrConstants;
/**
* 转写切片ID生成类
*
* @author guizheng@iflytek.com
*/
class SliceIdGenerator
{
/**
* @var string 当前切片ID
*/
private $id;
public function __construct()
{
$this->id = LfasrConstants::ORIGIN_SLICE_ID;
}
/**
* 返回当前切片ID并生成下一个切片ID赋值给对象的当前ID
*
* @return string
*/
public function getId()
{
$currentId = $this->id;
$nextId = $currentId;
$pos = strlen($currentId) - 1;
while ($pos >= 0) {
$charAtPos = $nextId[$pos];
if ($charAtPos != 'z') {
$nextId = substr($nextId, 0, $pos) . chr((ord($charAtPos) + 1)) . substr($nextId, $pos + 1);
break;
} else {
$nextId = substr($nextId, 0, $pos) . 'a';
$pos = $pos - 1;
}
}
$this->id = $nextId;
return $currentId;
}
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech;
use Exception;
use IFlytek\Xfyun\Speech\Config\IgrConfig;
use IFlytek\Xfyun\Speech\Constants\IgrConstants;
use IFlytek\Xfyun\Speech\Traits\IgrTrait;
use IFlytek\Xfyun\Core\Handler\WsHandler;
use IFlytek\Xfyun\Core\WsClient;
use IFlytek\Xfyun\Core\Traits\SignTrait;
use GuzzleHttp\Psr7\Stream;
/**
* 性别年龄识别客户端
*
* @author guizheng@iflytek.com
*/
class IgrClient
{
use SignTrait;
use IgrTrait;
/**
* @var string app_id
*/
protected $appId;
/**
* @var string api_key
*/
protected $apiKey;
/**
* @var string api_secret
*/
protected $apiSecret;
/**
* @var IgrConfig
*/
protected $requestConfig;
public function __construct($appId, $apiKey, $apiSecret, $requestConfig = [])
{
$this->appId = $appId;
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->requestConfig = new IgrConfig($requestConfig);
}
/**
* 请求并返回结果
*
* @param string $audioPath 待识别音频路径
* @return string
* @throws Exception
*/
public function request($audioPath)
{
$ttsHandler = new WsHandler(
$this->signUriV1(IgrConstants::URI, [
'appId' => $this->appId,
'apiKey' => $this->apiKey,
'apiSecret' => $this->apiSecret,
'host' => IgrConstants::HOST,
'requestLine' => IgrConstants::REQUEST_LINE,
]),
null
);
$client = new WsClient([
'handler' => $ttsHandler
]);
// 音频上传
$frameNum = ceil(fileSize($audioPath) / IgrConstants::FRAME_SIZE);
$fileStream = new Stream(fopen($audioPath, 'r'));
// 发送第一帧
$client->send($this->generateAudioInput($fileStream->read(IgrConstants::FRAME_SIZE), true, false));
// 发送中间帧
for ($i = 1; $i < $frameNum; $i++) {
$client->send($this->generateAudioInput($fileStream->read(IgrConstants::FRAME_SIZE), false, false));
usleep(4000);
}
// 发送最后一帧
$client->send($this->generateAudioInput('', false, true));
// 接受数据
$message = $this->jsonDecode($client->receive(), true);
if ($message['code'] !== 0) {
throw new Exception(json_encode($message));
}
return $message['data'];
}
}

View File

@ -0,0 +1,126 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech;
use IFlytek\Xfyun\Speech\Config\IseConfig;
use IFlytek\Xfyun\Speech\Constants\IseConstants;
use IFlytek\Xfyun\Speech\Traits\IseTrait;
use IFlytek\Xfyun\Core\Handler\WsHandler;
use IFlytek\Xfyun\Core\WsClient;
use IFlytek\Xfyun\Core\Traits\SignTrait;
use GuzzleHttp\Psr7\Stream;
/**
* 语音评测客户端
*
* @author guizheng@iflytek.com
*/
class IseClient
{
use SignTrait;
use IseTrait;
/**
* @var string app_id
*/
protected $appId;
/**
* @var string api_key
*/
protected $apiKey;
/**
* @var string api_secret
*/
protected $apiSecret;
/**
* @var array 评测参数配置
*/
protected $requestConfig;
public function __construct($appId, $apiKey, $apiSecret, $requestConfig = [])
{
$this->appId = $appId;
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->requestConfig = new IseConfig($requestConfig);
}
/**
* 请求评测并返回结果xml格式
*
* @param string $audioPath 待评测音频路径
* @param string $text 待评测文本
* @return string
*/
public function request($audioPath, $text = null)
{
$ttsHandler = new WsHandler(
$this->signUriV1(IseConstants::URI, [
'appId' => $this->appId,
'apiKey' => $this->apiKey,
'apiSecret' => $this->apiSecret,
'host' => IseConstants::HOST,
'requestLine' => IseConstants::REQUEST_LINE,
]),
null
);
$client = new WsClient([
'handler' => $ttsHandler
]);
// 参数上传
if (!empty($text)) {
$this->requestConfig->setText($text);
}
$client->send($this->generateParamsInput($this->appId, $this->requestConfig->toArray()));
// 音频上传
$frameSize = ceil(fileSize($audioPath) / IseConstants::FRAME_SIZE);
$fileStream = new Stream(fopen($audioPath, 'r'));
// 发送第一帧
$result = $client->send($this->generateAudioInput($fileStream->read(IseConstants::FRAME_SIZE), true, false));
// 发送中间帧
for ($i = 1; $i < $frameSize - 1; $i++) {
$client->send($this->generateAudioInput($fileStream->read(IseConstants::FRAME_SIZE), false, false));
usleep(4000);
}
// 发送最后一帧
$client->send($this->generateAudioInput($fileStream->read(IseConstants::FRAME_SIZE), false, true));
// 接受数据
$result = '';
while (true) {
$message = $this->jsonDecode($client->receive());
if ($message->code !== 0) {
throw new \Exception(json_encode($message));
}
switch ($message->data->status) {
case 1:
$result .= base64_decode($message->data->data);
break;
case 2:
$result .= base64_decode($message->data->data);
break 2;
}
}
return $result;
}
}

View File

@ -0,0 +1,231 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech;
use Exception;
use IFlytek\Xfyun\Speech\Config\LfasrConfig;
use IFlytek\Xfyun\Speech\Constants\LfasrConstants;
use IFlytek\Xfyun\Speech\Traits\LfasrTrait;
use IFlytek\Xfyun\Speech\Helper\SliceIdGenerator;
use IFlytek\Xfyun\Core\Traits\SignTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
use IFlytek\Xfyun\Core\HttpClient;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Query;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\MultipartStream;
/**
* 语音转写客户端
*
* @author guizheng@iflytek.com
*/
class LfasrClient
{
use SignTrait;
use JsonTrait;
use LfasrTrait;
/**
* @var string app_id
*/
protected $appId;
/**
* @var string secret_key
*/
protected $secretKey;
/**
* @var array 转写参数配置
*/
protected $requestConfig;
/** @var HttpClient */
protected $client;
/** @var array 初始化请求体 */
protected $requestBody;
public function __construct($appId, $secretKey, $requestConfig = [])
{
$this->appId = $appId;
$this->secretKey = $secretKey;
$this->requestConfig = new LfasrConfig($requestConfig);
$this->client = new HttpClient([]);
$timestamp = time();
$this->requestBody = [
'app_id' => $this->appId,
'signa' => $this->signV1($this->appId, $this->secretKey, $timestamp),
'ts' => $timestamp,
];
}
/**
* 打包上传接口封装了准备、分片上传和合并三个接口并返回task_id
*
* @param string $filePath 文件路径
* @return string
* @throws Exception
*/
public function combineUpload($filePath)
{
$prepareResponse = $this->prepare($filePath);
$prepareContent = $this->jsonDecode($prepareResponse->getBody()->getContents(), true);
$taskId = $prepareContent['data'];
$this->upload($taskId, $filePath);
$this->merge($taskId);
return $taskId;
}
/**
* 准备接口
*
* @param $file_path
* @return Response
* @throws Exception
*/
public function prepare($file_path)
{
$this->requestBody += $this->fileInfo($file_path);
$this->requestBody += $this->sliceInfo($file_path);
$this->requestBody += $this->requestConfig->toArray();
return $this->client->sendAndReceive(
new Request(
'POST',
LfasrConstants::URI_PREPARE,
['Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'],
Query::build($this->requestBody)
)
);
}
/**
* 分片上传接口
*
* @param string $taskId task_id
* @param string $filePath 文件路径
* @return Response
* @throws Exception
*/
public function upload($taskId, $filePath)
{
$sliceIdGenerator = new SliceIdGenerator();
$sliceInfo = $this->sliceInfo($filePath);
$fileStream = new Stream(fopen($filePath, 'r'));
$this->requestBody += [
'task_id' => $taskId
];
$request = new Request(
'POST',
LfasrConstants::URI_UPLOAD
);
for ($i = 0; $i < $sliceInfo['slice_num']; $i++) {
$multipartStream = new MultipartStream([
[
'name' => 'app_id',
'contents' => $this->requestBody['app_id']
],
[
'name' => 'signa',
'contents' => $this->requestBody['signa']
],
[
'name' => 'ts',
'contents' => $this->requestBody['ts']
],
[
'name' => 'task_id',
'contents' => $taskId,
],
[
'name' => 'slice_id',
'contents' => $sliceIdGenerator->getId()
],
[
'name' => 'content',
'contents' => $fileStream->read(LfasrConstants::SLICE_PIECE_SIZE),
'filename' => '1.pcm'
]
]);
$this->client->sendAndReceive(
$request->withBody($multipartStream)
);
}
return new Response(200);
}
/**
* 合并接口
*
* @param string $taskId task_id
* @return Response
*/
public function merge($taskId)
{
return $this->process(LfasrConstants::URI_MERGE, $taskId);
}
/**
* 查询进度接口
*
* @param string $taskId task_id
* @return Response
*/
public function getProgress($taskId)
{
return $this->process(LfasrConstants::URI_GET_PROGRESS, $taskId);
}
/**
* 获取结果接口
*
* @param string $taskId task_id
* @return Response
*/
public function getResult($taskId)
{
return $this->process(LfasrConstants::URI_GET_RESULT, $taskId);
}
/**
* 封装操作
*
* @param string $task 操作
* @param string $taskId task_id
* @return Response
*/
private function process($task, $taskId)
{
$this->requestBody += ['task_id' => $taskId];
return $this->client->sendAndReceive(
new Request(
'POST',
$task,
['Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'],
Query::build($this->requestBody)
)
);
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* Copyright 1999-2022 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech;
use IFlytek\Xfyun\Speech\Config\TcConfig;
use IFlytek\Xfyun\Speech\Constants\TcConstants;
use IFlytek\Xfyun\Speech\Traits\TcTrait;
use IFlytek\Xfyun\Core\Traits\SignTrait;
use IFlytek\Xfyun\Core\HttpClient;
use GuzzleHttp\Psr7\Request;
/**
* 文本纠错客户端
*
* @author guizheng@iflytek.com
*/
class TcClient
{
use SignTrait;
use TcTrait;
protected $appId;
protected $apiKey;
protected $apiSecret;
protected $uid;
protected $resId;
protected $requestConfig;
protected $client;
public function __construct($appId, $apiKey, $apiSecret, $uid = null, $resId = null, $requestConfig = [])
{
$this->appId = $appId;
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->uid = $uid;
$this->resId = $resId;
$this->requestConfig = new TcConfig($requestConfig);
$this->client = new HttpClient([]);
}
public function request($text)
{
$uri = self::signUriV1(TcConstants::URI, [
'apiKey' => $this->apiKey,
'apiSecret' => $this->apiSecret,
'host' => TcConstants::HOST,
'requestLine' => TcConstants::REQUEST_LINE
]);
$body = self::generateInput($text, $this->appId, $this->uid, $this->resId, $this->requestConfig->toArray());
return $this->client->sendAndReceive(
new Request(
'POST',
$uri,
['Content-Type' => 'application/json;charset=UTF-8'],
$body
)
);
}
public function listUpload($whiteList, $blackList)
{
if (empty($this->uid) || empty($this->resId)) {
return false;
}
$response = $this->client->sendAndReceive(
new Request(
'POST',
TcConstants::LIST_UPLOAD_URI,
['Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'],
self::jsonEncode([
'common' => [
'app_id' => $this->appId,
'uid' => $this->uid,
],
'business' => [
'res_id' => $this->resId
],
'data' => base64_encode(self::jsonEncode([
'white_list' => $whiteList,
'black_list' => $blackList
]))
])
)
);
$content = json_decode($response->getBody()->getContents(), true);
return $content['message'] == 'success';
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Traits;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
/**
* 性别年龄识别方法
*
* @author guizheng@iflytek.com
*/
trait IgrTrait
{
use ArrayTrait;
use JsonTrait;
/**
* 根据音频数据、是否是第一帧、最后一帧,生成音频上传请求体
*
* @param string $frameData 音频数据
* @param boolean $isFirstFrame 是否是第一帧
* @param boolean $isLastFrame 是否是最后一帧
* @return string
*/
public function generateAudioInput($frameData, $isFirstFrame = false, $isLastFrame = false)
{
return self::jsonEncode(
self::removeNull([
"common" => !$isFirstFrame ? null : [
"app_id" => $this->appId
],
'business' => !$isFirstFrame ? null : [
"aue" => $this->requestConfig->getAue(),
"rate" => $this->requestConfig->getRate()
],
'data' => [
'status' => $isFirstFrame ? 0 : ($isLastFrame ? 2 : 1),
'audio' => base64_encode($frameData)
]
])
);
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Traits;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
/**
* 语音评测方法
*
* @author guizheng@iflytek.com
*/
trait IseTrait
{
use ArrayTrait;
use JsonTrait;
/**
* 根据合成内容、app_id、配置参数生成请求体
*
* @param string $appId app_id
* @param array $iseConfigArray 语音合成参数详见iseConfig
* @return string
*/
public static function generateParamsInput($appId, $iseConfigArray)
{
return self::jsonEncode(
self::removeNull([
'common' => [
'app_id' => $appId
],
'business' => $iseConfigArray,
'data' => [
'status' => 0
]
])
);
}
/**
* 根据音频数据、是否是第一帧、最后一帧,生成音频上传请求体
*
* @param string $frameData 音频数据
* @param boolean $isFirstFrame 是否是第一帧
* @param boolean $isLastFrame 是否是最后一帧
* @return string
*/
public static function generateAudioInput($frameData, $isFirstFrame = false, $isLastFrame = false)
{
return self::jsonEncode(
self::removeNull([
'business' => [
"cmd" => "auw",
"aus" => $isFirstFrame ? 1 : ($isLastFrame ? 4 : 2)
],
'data' => [
'status' => $isLastFrame ? 2 : 1,
'data' => base64_encode($frameData)
]
])
);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Traits;
use IFlytek\Xfyun\Speech\Constants\LfasrConstants;
/**
* 转写方法
*
* @author guizheng@iflytek.com
*/
trait LfasrTrait
{
/**
* 获取文件名和文件大小
*
* @param string $filePath 文件路径
* @return array
*/
public static function fileInfo($filePath)
{
return [
'file_name' => basename($filePath),
'file_len' => filesize($filePath)
];
}
/**
* 根据文件大小和SDK默认分片大小获取文件分片数目信息
*
* @param string $filePath 文件路径
* @return array
*/
public static function sliceInfo($filePath)
{
$fileSize = filesize($filePath);
return [
'slice_num' => ceil($fileSize / LfasrConstants::SLICE_PIECE_SIZE)
];
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Traits;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
/**
* 文本纠错方法
*
* @author guizheng@iflytek.com
*/
trait TcTrait
{
use ArrayTrait;
use JsonTrait;
public static function generateInput($text, $appId, $uid, $resId, $tcConfigArray)
{
return self::jsonEncode(
self::removeNull([
'header' => [
'app_id' => $appId,
'uid' => $uid,
'status' => 3
],
'parameter' => [
's9a87e3ec' => [
'res_id' => $resId,
'result' => [
'encoding' => $tcConfigArray['resultEncoding'],
'compress' => $tcConfigArray['resultCompress'],
'format' => $tcConfigArray['resultFormat'],
]
]
],
'payload' => [
'input' => [
'encoding' => $tcConfigArray['inputEncoding'],
'compress' => $tcConfigArray['inputCompress'],
'format' => $tcConfigArray['inputFormat'],
'status' => 3,
'text' => base64_encode($text)
]
]
])
);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech\Traits;
use IFlytek\Xfyun\Core\Traits\ArrayTrait;
use IFlytek\Xfyun\Core\Traits\JsonTrait;
/**
* 语音合成方法
*
* @author guizheng@iflytek.com
*/
trait TtsTrait
{
use ArrayTrait;
use JsonTrait;
/**
* 根据合成内容、app_id、配置参数生成请求体
*
* @param string $text 带合成的文本
* @param string $appId app_id
* @param array $ttsConfigArray 语音合成参数详见TtsConfig
* @return string
*/
public static function generateInput($text, $appId, $ttsConfigArray)
{
return self::jsonEncode(
self::removeNull([
'common' => [
'app_id' => $appId
],
'business' => $ttsConfigArray,
'data' => [
'text' => base64_encode($text),
'status' => 2
]
])
);
}
}

View File

@ -0,0 +1,100 @@
<?php
/**
* Copyright 1999-2021 iFLYTEK Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace IFlytek\Xfyun\Speech;
use Exception;
use IFlytek\Xfyun\Speech\Config\TtsConfig;
use IFlytek\Xfyun\Speech\Constants\TtsConstants;
use IFlytek\Xfyun\Speech\Traits\TtsTrait;
use IFlytek\Xfyun\Core\Handler\WsHandler;
use IFlytek\Xfyun\Core\WsClient;
use IFlytek\Xfyun\Core\Traits\SignTrait;
use Psr\Log\LoggerInterface;
/**
* 语音合成客户端
*
* @author guizheng@iflytek.com
*/
class TtsClient
{
use SignTrait;
use TtsTrait;
/**
* @var string app_id
*/
protected $appId;
/**
* @var string api_key
*/
protected $apiKey;
/**
* @var string api_secret
*/
protected $apiSecret;
/**
* @var array 合成参数配置
*/
protected $requestConfig;
/**
* @var LoggerInterface or null 日志处理
*/
protected $logger;
public function __construct($appId, $apiKey, $apiSecret, $requestConfig = [], $logger = null)
{
$this->appId = $appId;
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->requestConfig = new TtsConfig($requestConfig);
$this->logger = $logger;
}
/**
* 合成文本,并返回结果(字节数组)在Response->getBody()->getContents()
*
* @param string $text 待合成的文本
* @return \GuzzleHttp\Psr7\Response
* @throws Exception
*/
public function request($text)
{
$ttsHandler = new WsHandler(
$this->signUriV1(TtsConstants::URI, [
'appId' => $this->appId,
'apiKey' => $this->apiKey,
'apiSecret' => $this->apiSecret,
'host' => TtsConstants::HOST,
'requestLine' => TtsConstants::REQUEST_LINE,
]),
$this->generateInput($text, $this->appId, $this->requestConfig->toArray())
);
if ($this->logger) {
$ttsHandler->setLogger($this->logger);
}
$client = new WsClient([
'handler' => $ttsHandler
]);
return $client->sendAndReceive();
}
}

4
app/functions.php Normal file
View File

@ -0,0 +1,4 @@
<?php
/**
* Here is your custom functions.
*/

View File

@ -0,0 +1,42 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* Class StaticFile
* @package app\middleware
*/
class StaticFile implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
// Access to files beginning with. Is prohibited
if (strpos($request->path(), '/.') !== false) {
return response('<h1>403 forbidden</h1>', 403);
}
/** @var Response $response */
$response = $next($request);
// Add cross domain HTTP header
/*$response->withHeaders([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
]);*/
return $response;
}
}

29
app/model/Test.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace app\model;
use support\Model;
class Test extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'test';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}

14
app/view/index/view.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico"/>
<title>webman</title>
</head>
<body>
hello <?=htmlspecialchars($name)?>
</body>
</html>

57
composer.json Normal file
View File

@ -0,0 +1,57 @@
{
"name": "workerman/webman",
"type": "project",
"keywords": [
"high performance",
"http service"
],
"homepage": "https://www.workerman.net",
"license": "MIT",
"description": "High performance HTTP Service Framework.",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/webman/issues",
"forum": "https://wenda.workerman.net/",
"wiki": "https://workerman.net/doc/webman",
"source": "https://github.com/walkor/webman"
},
"require": {
"php": ">=7.2",
"workerman/webman-framework": "^1.5.0",
"monolog/monolog": "^2.0",
"textalk/websocket": "^1.6"
},
"suggest": {
"ext-event": "For better performance. "
},
"autoload": {
"psr-4": {
"": "./",
"app\\": "./app",
"App\\": "./app",
"app\\View\\Components\\": "./app/view/components"
},
"files": [
"./support/helpers.php"
]
},
"scripts": {
"post-package-install": [
"support\\Plugin::install"
],
"post-package-update": [
"support\\Plugin::install"
],
"pre-package-uninstall": [
"support\\Plugin::uninstall"
]
}
}

533
composer.lock generated Normal file
View File

@ -0,0 +1,533 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "742c251f2e4bc7f8e8ebe86c60fdac51",
"packages": [
{
"name": "monolog/monolog",
"version": "2.9.1",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/monolog/monolog/2.9.1/monolog-monolog-2.9.1.zip",
"reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^0.12.91",
"phpunit/phpunit": "^8.5.14",
"predis/predis": "^1.1 || ^2.0",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"time": "2023-02-06T13:44:46+00:00"
},
{
"name": "nikic/fast-route",
"version": "v1.3.0",
"dist": {
"type": "zip",
"url": "https://mirrors.tencent.com/repository/composer/nikic/fast-route/v1.3.0/nikic-fast-route-v1.3.0.zip",
"reference": "181d480e08d9476e61381e04a71b34dc0432e812",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35|~5.7"
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"FastRoute\\": "src/"
}
},
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov",
"email": "nikic@php.net"
}
],
"description": "Fast request router for PHP",
"keywords": [
"router",
"routing"
],
"time": "2018-02-13T20:26:39+00:00"
},
{
"name": "phrity/net-uri",
"version": "1.3.0",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/phrity/net-uri/1.3.0/phrity-net-uri-1.3.0.zip",
"reference": "3f458e0c4d1ddc0e218d7a5b9420127c63925f43",
"shasum": ""
},
"require": {
"php": "^7.4 | ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0 | ^2.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^9.0 | ^10.0",
"squizlabs/php_codesniffer": "^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Phrity\\Net\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Sören Jensen",
"email": "sirn@sirn.se",
"homepage": "https://phrity.sirn.se"
}
],
"description": "PSR-7 Uri and PSR-17 UriFactory implementation",
"homepage": "https://phrity.sirn.se/net-uri",
"keywords": [
"psr-17",
"psr-7",
"uri",
"uri factory"
],
"time": "2023-08-21T10:33:06+00:00"
},
{
"name": "phrity/util-errorhandler",
"version": "1.0.1",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/phrity/util-errorhandler/1.0.1/phrity-util-errorhandler-1.0.1.zip",
"reference": "dc9ac8fb70d733c48a9d9d1eb50f7022172da6bc",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^8.0|^9.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"autoload": {
"psr-4": {
"": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Sören Jensen",
"email": "sirn@sirn.se",
"homepage": "https://phrity.sirn.se"
}
],
"description": "Inline error handler; catch and resolve errors for code block.",
"homepage": "https://phrity.sirn.se/util-errorhandler",
"keywords": [
"error",
"warning"
],
"time": "2022-10-27T12:14:42+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/psr/container/2.0.2/psr-container-2.0.2.zip",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"time": "2021-11-05T16:47:00+00:00"
},
{
"name": "psr/http-factory",
"version": "1.0.2",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/psr/http-factory/1.0.2/psr-http-factory-1.0.2.zip",
"reference": "e616d01114759c4c489f93b099585439f795fe35",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"time": "2023-04-10T20:10:41+00:00"
},
{
"name": "psr/http-message",
"version": "1.1",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/psr/http-message/1.1/psr-http-message-1.1.zip",
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2023-04-04T09:50:52+00:00"
},
{
"name": "psr/log",
"version": "3.0.0",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/psr/log/3.0.0/psr-log-3.0.0.zip",
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2021-07-14T16:46:02+00:00"
},
{
"name": "textalk/websocket",
"version": "1.6.3",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/textalk/websocket/1.6.3/textalk-websocket-1.6.3.zip",
"reference": "67de79745b1a357caf812bfc44e0abf481cee012",
"shasum": ""
},
"require": {
"php": "^7.4 | ^8.0",
"phrity/net-uri": "^1.0",
"phrity/util-errorhandler": "^1.0",
"psr/http-message": "^1.0",
"psr/log": "^1.0 | ^2.0 | ^3.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"autoload": {
"psr-4": {
"WebSocket\\": "lib"
}
},
"license": [
"ISC"
],
"authors": [
{
"name": "Fredrik Liljegren"
},
{
"name": "Sören Jensen"
}
],
"description": "WebSocket client and server",
"time": "2022-11-07T18:59:33+00:00"
},
{
"name": "workerman/webman-framework",
"version": "v1.5.9",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/workerman/webman-framework/v1.5.9/workerman-webman-framework-v1.5.9.zip",
"reference": "6c43faa4f8e3ec61b8d97ba9e8ff53965b9a8b69",
"shasum": ""
},
"require": {
"ext-json": "*",
"nikic/fast-route": "^1.3",
"php": ">=7.2",
"psr/container": ">=1.0",
"workerman/workerman": "^4.0.4 || ^5.0.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"type": "library",
"autoload": {
"psr-4": {
"Webman\\": "./src",
"Support\\": "./src/support",
"support\\": "./src/support",
"Support\\View\\": "./src/support/view",
"Support\\Bootstrap\\": "./src/support/bootstrap",
"Support\\Exception\\": "./src/support/exception"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"description": "High performance HTTP Service Framework.",
"homepage": "https://www.workerman.net",
"keywords": [
"High Performance",
"http service"
],
"time": "2023-09-11T07:55:50+00:00"
},
{
"name": "workerman/workerman",
"version": "v4.1.13",
"dist": {
"type": "zip",
"url": "https://mirrors.tencent.com/repository/composer/workerman/workerman/v4.1.13/workerman-workerman-v4.1.13.zip",
"reference": "807780ff672775fcd08f89e573a2824e939021ce",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"type": "library",
"autoload": {
"psr-4": {
"Workerman\\": "./"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net",
"keywords": [
"asynchronous",
"event-loop"
],
"time": "2023-07-31T05:57:25+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.2"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

BIN
composer.phar Executable file

Binary file not shown.

26
config/app.php Normal file
View File

@ -0,0 +1,26 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\Request;
return [
'debug' => true,
'error_reporting' => E_ALL,
'default_timezone' => 'Asia/Shanghai',
'request_class' => Request::class,
'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
'controller_suffix' => 'Controller',
'controller_reuse' => false,
];

21
config/autoload.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'files' => [
base_path() . '/app/functions.php',
base_path() . '/support/Request.php',
base_path() . '/support/Response.php',
]
];

18
config/bootstrap.php Normal file
View File

@ -0,0 +1,18 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
support\bootstrap\Session::class,
support\bootstrap\LaravelDb::class,
];

15
config/container.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return new Webman\Container;

15
config/database.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

15
config/dependence.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

17
config/exception.php Normal file
View File

@ -0,0 +1,17 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'' => support\exception\Handler::class,
];

32
config/log.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'default' => [
'handlers' => [
[
'class' => Monolog\Handler\RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/webman.log',
7, //$maxFiles
Monolog\Logger::DEBUG,
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [null, 'Y-m-d H:i:s', true],
],
]
],
],
];

15
config/middleware.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

49
config/process.php Normal file
View File

@ -0,0 +1,49 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
global $argv;
return [
// File update detection and automatic reload
'monitor' => [
'handler' => process\Monitor::class,
'reloadable' => false,
'constructor' => [
// Monitor these directories
'monitorDir' => array_merge([
app_path(),
config_path(),
base_path() . '/process',
base_path() . '/support',
base_path() . '/resource',
base_path() . '/.env',
], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')),
// Files with these suffixes will be monitored
'monitorExtensions' => [
'php', 'html', 'htm', 'env'
],
'options' => [
'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/',
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
]
]
],
'websocket_test' => [
// 这里指定进程类就是上面定义的Pusher类
'handler' => app\Pusher::class,
'listen' => 'websocket://0.0.0.0:8888',
'count' => 1,
],
];

22
config/redis.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0,
],
];

21
config/route.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Route;

31
config/server.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'listen' => 'http://0.0.0.0:8787',
'transport' => 'tcp',
'context' => [],
'name' => 'webman',
'count' => cpu_count() * 4,
'user' => '',
'group' => '',
'reusePort' => false,
'event_loop' => '',
'stop_timeout' => 2,
'pid_file' => runtime_path() . '/webman.pid',
'status_file' => runtime_path() . '/webman.status',
'stdout_file' => runtime_path() . '/logs/stdout.log',
'log_file' => runtime_path() . '/logs/workerman.log',
'max_package_size' => 10 * 1024 * 1024
];

65
config/session.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Session\FileSessionHandler;
use Webman\Session\RedisSessionHandler;
use Webman\Session\RedisClusterSessionHandler;
return [
'type' => 'file', // or redis or redis_cluster
'handler' => FileSessionHandler::class,
'config' => [
'file' => [
'save_path' => runtime_path() . '/sessions',
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'auth' => '',
'timeout' => 2,
'database' => '',
'prefix' => 'redis_session_',
],
'redis_cluster' => [
'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
'timeout' => 2,
'auth' => '',
'prefix' => 'redis_session_',
]
],
'session_name' => 'PHPSID',
'auto_update_timestamp' => false,
'lifetime' => 7*24*60*60,
'cookie_lifetime' => 365*24*60*60,
'cookie_path' => '/',
'domain' => '',
'http_only' => true,
'secure' => false,
'same_site' => '',
'gc_probability' => [1, 1000],
];

23
config/static.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Static file settings
*/
return [
'enable' => true,
'middleware' => [ // Static file Middleware
//app\middleware\StaticFile::class,
],
];

25
config/translation.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Multilingual configuration
*/
return [
// Default language
'locale' => 'zh_CN',
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => base_path() . '/resource/translations',
];

22
config/view.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\view\Raw;
use support\view\Twig;
use support\view\Blade;
use support\view\ThinkPHP;
return [
'handler' => Raw::class
];

243
process/Monitor.php Normal file
View File

@ -0,0 +1,243 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace process;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use Workerman\Timer;
use Workerman\Worker;
/**
* Class FileMonitor
* @package process
*/
class Monitor
{
/**
* @var array
*/
protected $paths = [];
/**
* @var array
*/
protected $extensions = [];
/**
* @var string
*/
public static $lockFile = __DIR__ . '/../runtime/monitor.lock';
/**
* Pause monitor
* @return void
*/
public static function pause()
{
file_put_contents(static::$lockFile, time());
}
/**
* Resume monitor
* @return void
*/
public static function resume(): void
{
clearstatcache();
if (is_file(static::$lockFile)) {
unlink(static::$lockFile);
}
}
/**
* Whether monitor is paused
* @return bool
*/
public static function isPaused(): bool
{
clearstatcache();
return file_exists(static::$lockFile);
}
/**
* FileMonitor constructor.
* @param $monitorDir
* @param $monitorExtensions
* @param array $options
*/
public function __construct($monitorDir, $monitorExtensions, array $options = [])
{
static::resume();
$this->paths = (array)$monitorDir;
$this->extensions = $monitorExtensions;
if (!Worker::getAllWorkers()) {
return;
}
$disableFunctions = explode(',', ini_get('disable_functions'));
if (in_array('exec', $disableFunctions, true)) {
echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n";
} else {
if ($options['enable_file_monitor'] ?? true) {
Timer::add(1, function () {
$this->checkAllFilesChange();
});
}
}
$memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null);
if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) {
Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]);
}
}
/**
* @param $monitorDir
* @return bool
*/
public function checkFilesChange($monitorDir): bool
{
static $lastMtime, $tooManyFilesCheck;
if (!$lastMtime) {
$lastMtime = time();
}
clearstatcache();
if (!is_dir($monitorDir)) {
if (!is_file($monitorDir)) {
return false;
}
$iterator = [new SplFileInfo($monitorDir)];
} else {
// recursive traversal directory
$dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS);
$iterator = new RecursiveIteratorIterator($dirIterator);
}
$count = 0;
foreach ($iterator as $file) {
$count ++;
/** var SplFileInfo $file */
if (is_dir($file->getRealPath())) {
continue;
}
// check mtime
if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) {
$var = 0;
exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var);
$lastMtime = $file->getMTime();
if ($var) {
continue;
}
echo $file . " update and reload\n";
// send SIGUSR1 signal to master process for reload
if (DIRECTORY_SEPARATOR === '/') {
posix_kill(posix_getppid(), SIGUSR1);
} else {
return true;
}
break;
}
}
if (!$tooManyFilesCheck && $count > 1000) {
echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n";
$tooManyFilesCheck = 1;
}
return false;
}
/**
* @return bool
*/
public function checkAllFilesChange(): bool
{
if (static::isPaused()) {
return false;
}
foreach ($this->paths as $path) {
if ($this->checkFilesChange($path)) {
return true;
}
}
return false;
}
/**
* @param $memoryLimit
* @return void
*/
public function checkMemory($memoryLimit)
{
if (static::isPaused() || $memoryLimit <= 0) {
return;
}
$ppid = posix_getppid();
$childrenFile = "/proc/$ppid/task/$ppid/children";
if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) {
return;
}
foreach (explode(' ', $children) as $pid) {
$pid = (int)$pid;
$statusFile = "/proc/$pid/status";
if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) {
continue;
}
$mem = 0;
if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
$mem = $match[1];
}
$mem = (int)($mem / 1024);
if ($mem >= $memoryLimit) {
posix_kill($pid, SIGINT);
}
}
}
/**
* Get memory limit
* @return float
*/
protected function getMemoryLimit($memoryLimit)
{
if ($memoryLimit === 0) {
return 0;
}
$usePhpIni = false;
if (!$memoryLimit) {
$memoryLimit = ini_get('memory_limit');
$usePhpIni = true;
}
if ($memoryLimit == -1) {
return 0;
}
$unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]);
if ($unit === 'g') {
$memoryLimit = 1024 * (int)$memoryLimit;
} else if ($unit === 'm') {
$memoryLimit = (int)$memoryLimit;
} else if ($unit === 'k') {
$memoryLimit = ((int)$memoryLimit / 1024);
} else {
$memoryLimit = ((int)$memoryLimit / (1024 * 1024));
}
if ($memoryLimit < 30) {
$memoryLimit = 30;
}
if ($usePhpIni) {
$memoryLimit = (int)(0.8 * $memoryLimit);
}
return $memoryLimit;
}
}

12
public/404.html Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
<title>404 Not Found - webman</title>
</head>
<body>
<center>
<h1>404 Not Found</h1>
</center>
<hr>
<center>webman</center>
</body>
</html>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

4
runtime/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*
!logs
!views
!.gitignore

2
runtime/logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
runtime/views/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

4
start.php Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
support\App::run();

24
support/Request.php Normal file
View File

@ -0,0 +1,24 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support;
/**
* Class Request
* @package support
*/
class Request extends \Webman\Http\Request
{
}

24
support/Response.php Normal file
View File

@ -0,0 +1,24 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support;
/**
* Class Response
* @package support
*/
class Response extends \Webman\Http\Response
{
}

133
support/bootstrap.php Normal file
View File

@ -0,0 +1,133 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Dotenv\Dotenv;
use support\Log;
use Webman\Bootstrap;
use Webman\Config;
use Webman\Middleware;
use Webman\Route;
use Webman\Util;
$worker = $worker ?? null;
set_error_handler(function ($level, $message, $file = '', $line = 0) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
});
if ($worker) {
register_shutdown_function(function ($startTime) {
if (time() - $startTime <= 0.1) {
sleep(1);
}
}, time());
}
if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) {
if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) {
Dotenv::createUnsafeMutable(base_path(false))->load();
} else {
Dotenv::createMutable(base_path(false))->load();
}
}
Config::clear();
support\App::loadAllConfig(['route']);
if ($timezone = config('app.default_timezone')) {
date_default_timezone_set($timezone);
}
foreach (config('autoload.files', []) as $file) {
include_once $file;
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['autoload']['files'] ?? [] as $file) {
include_once $file;
}
}
foreach ($projects['autoload']['files'] ?? [] as $file) {
include_once $file;
}
}
Middleware::load(config('middleware', []));
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project) || $name === 'static') {
continue;
}
Middleware::load($project['middleware'] ?? []);
}
Middleware::load($projects['middleware'] ?? [], $firm);
if ($staticMiddlewares = config("plugin.$firm.static.middleware")) {
Middleware::load(['__static__' => $staticMiddlewares], $firm);
}
}
Middleware::load(['__static__' => config('static.middleware', [])]);
foreach (config('bootstrap', []) as $className) {
if (!class_exists($className)) {
$log = "Warning: Class $className setting in config/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $className */
$className::start($worker);
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['bootstrap'] ?? [] as $className) {
if (!class_exists($className)) {
$log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $className */
$className::start($worker);
}
}
foreach ($projects['bootstrap'] ?? [] as $className) {
/** @var string $className */
if (!class_exists($className)) {
$log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $className */
$className::start($worker);
}
}
$directory = base_path() . '/plugin';
$paths = [config_path()];
foreach (Util::scanDir($directory) as $path) {
if (is_dir($path = "$path/config")) {
$paths[] = $path;
}
}
Route::load($paths);

517
support/helpers.php Normal file
View File

@ -0,0 +1,517 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\Container;
use support\Request;
use support\Response;
use support\Translation;
use support\view\Blade;
use support\view\Raw;
use support\view\ThinkPHP;
use support\view\Twig;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Webman\App;
use Webman\Config;
use Webman\Route;
use Workerman\Protocols\Http\Session;
use Workerman\Worker;
// Project base path
define('BASE_PATH', dirname(__DIR__));
/**
* return the program execute directory
* @param string $path
* @return string
*/
function run_path(string $path = ''): string
{
static $runPath = '';
if (!$runPath) {
$runPath = is_phar() ? dirname(Phar::running(false)) : BASE_PATH;
}
return path_combine($runPath, $path);
}
/**
* if the param $path equal false,will return this program current execute directory
* @param string|false $path
* @return string
*/
function base_path($path = ''): string
{
if (false === $path) {
return run_path();
}
return path_combine(BASE_PATH, $path);
}
/**
* App path
* @param string $path
* @return string
*/
function app_path(string $path = ''): string
{
return path_combine(BASE_PATH . DIRECTORY_SEPARATOR . 'app', $path);
}
/**
* Public path
* @param string $path
* @return string
*/
function public_path(string $path = ''): string
{
static $publicPath = '';
if (!$publicPath) {
$publicPath = \config('app.public_path') ?: run_path('public');
}
return path_combine($publicPath, $path);
}
/**
* Config path
* @param string $path
* @return string
*/
function config_path(string $path = ''): string
{
return path_combine(BASE_PATH . DIRECTORY_SEPARATOR . 'config', $path);
}
/**
* Runtime path
* @param string $path
* @return string
*/
function runtime_path(string $path = ''): string
{
static $runtimePath = '';
if (!$runtimePath) {
$runtimePath = \config('app.runtime_path') ?: run_path('runtime');
}
return path_combine($runtimePath, $path);
}
/**
* Generate paths based on given information
* @param string $front
* @param string $back
* @return string
*/
function path_combine(string $front, string $back): string
{
return $front . ($back ? (DIRECTORY_SEPARATOR . ltrim($back, DIRECTORY_SEPARATOR)) : $back);
}
/**
* Response
* @param int $status
* @param array $headers
* @param string $body
* @return Response
*/
function response(string $body = '', int $status = 200, array $headers = []): Response
{
return new Response($status, $headers, $body);
}
/**
* Json response
* @param $data
* @param int $options
* @return Response
*/
function json($data, int $options = JSON_UNESCAPED_UNICODE): Response
{
return new Response(200, ['Content-Type' => 'application/json'], json_encode($data, $options));
}
/**
* Xml response
* @param $xml
* @return Response
*/
function xml($xml): Response
{
if ($xml instanceof SimpleXMLElement) {
$xml = $xml->asXML();
}
return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}
/**
* Jsonp response
* @param $data
* @param string $callbackName
* @return Response
*/
function jsonp($data, string $callbackName = 'callback'): Response
{
if (!is_scalar($data) && null !== $data) {
$data = json_encode($data);
}
return new Response(200, [], "$callbackName($data)");
}
/**
* Redirect response
* @param string $location
* @param int $status
* @param array $headers
* @return Response
*/
function redirect(string $location, int $status = 302, array $headers = []): Response
{
$response = new Response($status, ['Location' => $location]);
if (!empty($headers)) {
$response->withHeaders($headers);
}
return $response;
}
/**
* View response
* @param string $template
* @param array $vars
* @param string|null $app
* @param string|null $plugin
* @return Response
*/
function view(string $template, array $vars = [], string $app = null, string $plugin = null): Response
{
$request = \request();
$plugin = $plugin === null ? ($request->plugin ?? '') : $plugin;
$handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler');
return new Response(200, [], $handler::render($template, $vars, $app, $plugin));
}
/**
* Raw view response
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
* @throws Throwable
*/
function raw_view(string $template, array $vars = [], string $app = null): Response
{
return new Response(200, [], Raw::render($template, $vars, $app));
}
/**
* Blade view response
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function blade_view(string $template, array $vars = [], string $app = null): Response
{
return new Response(200, [], Blade::render($template, $vars, $app));
}
/**
* Think view response
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function think_view(string $template, array $vars = [], string $app = null): Response
{
return new Response(200, [], ThinkPHP::render($template, $vars, $app));
}
/**
* Twig view response
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
function twig_view(string $template, array $vars = [], string $app = null): Response
{
return new Response(200, [], Twig::render($template, $vars, $app));
}
/**
* Get request
* @return \Webman\Http\Request|Request|null
*/
function request()
{
return App::request();
}
/**
* Get config
* @param string|null $key
* @param $default
* @return array|mixed|null
*/
function config(string $key = null, $default = null)
{
return Config::get($key, $default);
}
/**
* Create url
* @param string $name
* @param ...$parameters
* @return string
*/
function route(string $name, ...$parameters): string
{
$route = Route::getByName($name);
if (!$route) {
return '';
}
if (!$parameters) {
return $route->url();
}
if (is_array(current($parameters))) {
$parameters = current($parameters);
}
return $route->url($parameters);
}
/**
* Session
* @param mixed $key
* @param mixed $default
* @return mixed|bool|Session
*/
function session($key = null, $default = null)
{
$session = \request()->session();
if (null === $key) {
return $session;
}
if (is_array($key)) {
$session->put($key);
return null;
}
if (strpos($key, '.')) {
$keyArray = explode('.', $key);
$value = $session->all();
foreach ($keyArray as $index) {
if (!isset($value[$index])) {
return $default;
}
$value = $value[$index];
}
return $value;
}
return $session->get($key, $default);
}
/**
* Translation
* @param string $id
* @param array $parameters
* @param string|null $domain
* @param string|null $locale
* @return string
*/
function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
$res = Translation::trans($id, $parameters, $domain, $locale);
return $res === '' ? $id : $res;
}
/**
* Locale
* @param string|null $locale
* @return string
*/
function locale(string $locale = null): string
{
if (!$locale) {
return Translation::getLocale();
}
Translation::setLocale($locale);
return $locale;
}
/**
* 404 not found
* @return Response
*/
function not_found(): Response
{
return new Response(404, [], file_get_contents(public_path() . '/404.html'));
}
/**
* Copy dir
* @param string $source
* @param string $dest
* @param bool $overwrite
* @return void
*/
function copy_dir(string $source, string $dest, bool $overwrite = false)
{
if (is_dir($source)) {
if (!is_dir($dest)) {
mkdir($dest);
}
$files = scandir($source);
foreach ($files as $file) {
if ($file !== "." && $file !== "..") {
copy_dir("$source/$file", "$dest/$file");
}
}
} else if (file_exists($source) && ($overwrite || !file_exists($dest))) {
copy($source, $dest);
}
}
/**
* Remove dir
* @param string $dir
* @return bool
*/
function remove_dir(string $dir): bool
{
if (is_link($dir) || is_file($dir)) {
return unlink($dir);
}
$files = array_diff(scandir($dir), array('.', '..'));
foreach ($files as $file) {
(is_dir("$dir/$file") && !is_link($dir)) ? remove_dir("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}
/**
* Bind worker
* @param $worker
* @param $class
*/
function worker_bind($worker, $class)
{
$callbackMap = [
'onConnect',
'onMessage',
'onClose',
'onError',
'onBufferFull',
'onBufferDrain',
'onWorkerStop',
'onWebSocketConnect',
'onWorkerReload'
];
foreach ($callbackMap as $name) {
if (method_exists($class, $name)) {
$worker->$name = [$class, $name];
}
}
if (method_exists($class, 'onWorkerStart')) {
call_user_func([$class, 'onWorkerStart'], $worker);
}
}
/**
* Start worker
* @param $processName
* @param $config
* @return void
*/
function worker_start($processName, $config)
{
$worker = new Worker($config['listen'] ?? null, $config['context'] ?? []);
$propertyMap = [
'count',
'user',
'group',
'reloadable',
'reusePort',
'transport',
'protocol',
];
$worker->name = $processName;
foreach ($propertyMap as $property) {
if (isset($config[$property])) {
$worker->$property = $config[$property];
}
}
$worker->onWorkerStart = function ($worker) use ($config) {
require_once base_path('/support/bootstrap.php');
if (isset($config['handler'])) {
if (!class_exists($config['handler'])) {
echo "process error: class {$config['handler']} not exists\r\n";
return;
}
$instance = Container::make($config['handler'], $config['constructor'] ?? []);
worker_bind($worker, $instance);
}
};
}
/**
* Get realpath
* @param string $filePath
* @return string
*/
function get_realpath(string $filePath): string
{
if (strpos($filePath, 'phar://') === 0) {
return $filePath;
} else {
return realpath($filePath);
}
}
/**
* Is phar
* @return bool
*/
function is_phar(): bool
{
return class_exists(Phar::class, false) && Phar::running();
}
/**
* Get cpu count
* @return int
*/
function cpu_count(): int
{
// Windows does not support the number of processes setting.
if (DIRECTORY_SEPARATOR === '\\') {
return 1;
}
$count = 4;
if (is_callable('shell_exec')) {
if (strtolower(PHP_OS) === 'darwin') {
$count = (int)shell_exec('sysctl -n machdep.cpu.core_count');
} else {
$count = (int)shell_exec('nproc');
}
}
return $count > 0 ? $count : 4;
}

3
windows.bat Normal file
View File

@ -0,0 +1,3 @@
CHCP 65001
php windows.php
pause

116
windows.php Normal file
View File

@ -0,0 +1,116 @@
<?php
/**
* Start file for windows
*/
require_once __DIR__ . '/vendor/autoload.php';
use Dotenv\Dotenv;
use process\Monitor;
use support\App;
use Workerman\Worker;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(base_path())->load();
} else {
Dotenv::createMutable(base_path())->load();
}
}
App::loadAllConfig(['route']);
$errorReporting = config('app.error_reporting');
if (isset($errorReporting)) {
error_reporting($errorReporting);
}
$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
if (!is_dir($runtimeProcessPath)) {
mkdir($runtimeProcessPath);
}
$processFiles = [
__DIR__ . DIRECTORY_SEPARATOR . 'start.php'
];
foreach (config('process', []) as $processName => $config) {
$processFiles[] = write_process_file($runtimeProcessPath, $processName, '');
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['process'] ?? [] as $processName => $config) {
$processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name");
}
}
foreach ($projects['process'] ?? [] as $processName => $config) {
$processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm);
}
}
function write_process_file($runtimeProcessPath, $processName, $firm): string
{
$processParam = $firm ? "plugin.$firm.$processName" : $processName;
$configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']";
$fileContent = <<<EOF
<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use Workerman\Worker;
use Webman\Config;
use support\App;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (is_callable('opcache_reset')) {
opcache_reset();
}
App::loadAllConfig(['route']);
worker_start('$processParam', $configParam);
if (DIRECTORY_SEPARATOR != "/") {
Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
}
Worker::runAll();
EOF;
$processFile = $runtimeProcessPath . DIRECTORY_SEPARATOR . "start_$processParam.php";
file_put_contents($processFile, $fileContent);
return $processFile;
}
if ($monitorConfig = config('process.monitor.constructor')) {
$monitor = new Monitor(...array_values($monitorConfig));
}
function popen_processes($processFiles)
{
$cmd = '"' . PHP_BINARY . '" ' . implode(' ', $processFiles);
$descriptorspec = [STDIN, STDOUT, STDOUT];
$resource = proc_open($cmd, $descriptorspec, $pipes, null, null, ['bypass_shell' => true]);
if (!$resource) {
exit("Can not execute $cmd\r\n");
}
return $resource;
}
$resource = popen_processes($processFiles);
echo "\r\n";
while (1) {
sleep(1);
if (!empty($monitor) && $monitor->checkAllFilesChange()) {
$status = proc_get_status($resource);
$pid = $status['pid'];
shell_exec("taskkill /F /T /PID $pid");
proc_close($resource);
$resource = popen_processes($processFiles);
}
}