first commit

This commit is contained in:
Your Name
2026-01-19 14:19:22 +08:00
commit fe2d9c1868
4777 changed files with 665503 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
composer.phar
/vendor/
/.idea/
composer.lock
# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock

21
vendor/hanson/foundation-sdk/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 HanSon
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.

25
vendor/hanson/foundation-sdk/README.md vendored Normal file
View File

@@ -0,0 +1,25 @@
# foundation-sdk
To create a sdk easiler!
[![Latest Stable Version](https://poser.pugx.org/hanson/foundation-sdk/v/stable)](https://packagist.org/packages/hanson/foundation-sdk)
[![Total Downloads](https://poser.pugx.org/hanson/foundation-sdk/downloads)](https://packagist.org/packages/hanson/foundation-sdk)
[![Latest Unstable Version](https://poser.pugx.org/hanson/foundation-sdk/v/unstable)](https://packagist.org/packages/hanson/foundation-sdk)
[![License](https://poser.pugx.org/hanson/foundation-sdk/license)](https://packagist.org/packages/hanson/foundation-sdk)
[![Monthly Downloads](https://poser.pugx.org/hanson/foundation-sdk/d/monthly)](https://packagist.org/packages/hanson/foundation-sdk)
[![Daily Downloads](https://poser.pugx.org/hanson/foundation-sdk/d/daily)](https://packagist.org/packages/hanson/foundation-sdk)
## What's inside Foundation-sdk
- [pimple](https://github.com/silexphp/Pimple) (Container)
- Http
- Log
## 安装要求
php 7 以上需要PHP5的可移步到其他人维护的仓库 https://github.com/CodeNauhc/foundation-sdk/tree/php5
## 中文实例教程
[SDK 开发高级版!揭开 Foundation-SDK 的神秘面纱](https://learnku.com/articles/15038/sdk-development-advanced-edition-uncover-the-mysterious-veil-of-foundation-sdk)
[干货!手把手教你写 SDK ](https://learnku.com/articles/14995/dried-food-hand-in-hand-to-teach-you-to-write-sdk)

View File

@@ -0,0 +1,27 @@
{
"name": "hanson/foundation-sdk",
"license": "MIT",
"authors": [
{
"name": "HanSon",
"email": "h@hanc.cc"
}
],
"require": {
"php": "^7.0|^8.0",
"pimple/pimple": "^3.0",
"doctrine/cache": "^1.6",
"symfony/http-foundation": "^3.3|^4.0|^5.0|^6.0",
"guzzlehttp/guzzle": "^6.2|^7.0",
"monolog/monolog": "^1.22|^2.0|^3.0",
"ext-curl": "*"
},
"autoload": {
"psr-4": {
"Hanson\\Foundation\\": "src/"
}
},
"require-dev": {
"phpunit/phpunit": "^9.3"
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Hanson\Foundation;
use Psr\Http\Message\RequestInterface;
abstract class AbstractAPI
{
/**
* Http instance.
*
* @var Http
*/
protected $http;
/**
* @return Http
*/
public function getHttp()
{
if (is_null($this->http)) {
$this->http = new Http();
}
if (count($this->http->getMiddlewares()) === 0) {
$this->middlewares();
}
return $this->http;
}
/**
* add headers.
*
* @param $headers
*
* @return \Closure
*/
protected function headerMiddleware($headers)
{
return function (callable $handler) use ($headers) {
return function (RequestInterface $request, array $options) use ($handler, $headers) {
foreach ($headers as $key => $header) {
$request = $request->withHeader($key, $header);
}
return $handler($request, $options);
};
};
}
/**
* Push guzzle middleware before request.
*
* @return mixed
*/
public function middlewares()
{
}
}

View File

@@ -0,0 +1,237 @@
<?php
namespace Hanson\Foundation;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\FilesystemCache;
abstract class AbstractAccessToken
{
/**
* App id.
*
* @var string
*/
protected $appId;
/**
* App secret.
*
* @var string
*/
protected $secret;
/**
* Cache key.
*
* @var string
*/
protected $cacheKey;
/**
* Cache.
*
* @var Cache
*/
protected $cache;
/**
* Cache key prefix.
*
* @var string
*/
protected $prefix;
/**
* Response Json key name of token.
*
* @var string
*/
protected $tokenJsonKey;
/**
* Response Json key name of expires in.
*
* @var string
*/
protected $expiresJsonKey;
/**
* @var Http
*/
protected $http;
/**
* @var Foundation
*/
protected $app;
/**
* Token string.
*
* @var string
*/
protected $token;
/**
* AbstractAccessToken constructor.
* @param Foundation $app
*/
public function __construct(Foundation $app)
{
$this->app = $app;
}
/**
* @param mixed $token
* @param int $expires
* @return $this
*/
public function setToken($token, $expires = 86400)
{
if ($expires) {
$this->getCache()->save($this->getCacheKey(), $token, $expires);
}
$this->token = $token;
return $this;
}
/**
* Get token from cache.
*
* @param bool $forceRefresh
*
* @return string
*/
public function getToken($forceRefresh = false)
{
$cached = $this->getCache()->fetch($this->getCacheKey()) ?: $this->token;
if ($forceRefresh || empty($cached)) {
$result = $this->getTokenFromServer();
$this->checkTokenResponse($result);
$this->setToken(
$token = $result[$this->tokenJsonKey],
$this->expiresJsonKey ? $result[$this->expiresJsonKey] : null
);
return $token;
}
return $cached;
}
/**
* Get token from remote server.
*
* @return mixed
*/
abstract public function getTokenFromServer();
/**
* Throw exception if token is invalid.
*
* @param $result
* @return mixed
*/
abstract public function checkTokenResponse($result);
/**
* @param mixed $appId
*/
public function setAppId($appId)
{
$this->appId = $appId;
}
/**
* @return mixed
*/
public function getAppId()
{
return $this->appId;
}
/**
* @param string $secret
*/
public function setSecret($secret)
{
$this->secret = $secret;
}
/**
* @return mixed
*/
public function getSecret()
{
return $this->secret;
}
/**
* Set cache instance.
*
* @param \Doctrine\Common\Cache\Cache $cache
*
* @return AbstractAccessToken
*/
public function setCache(Cache $cache)
{
$this->cache = $cache;
return $this;
}
/**
* Return the cache manager.
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getCache()
{
return $this->cache ?: $this->cache = new FilesystemCache(sys_get_temp_dir());
}
/**
* Return the cache key mix with appId.
*
* @return string
*/
public function getCacheKey()
{
if (is_null($this->cacheKey)) {
return $this->prefix.$this->appId;
}
return $this->cacheKey;
}
/**
* Return the http instance.
*
* @return Http
*/
public function getHttp()
{
return $this->http ?? $this->app->http;
}
/**
* Set the http instance.
*
* @param Http $http
*
* @return $this
*/
public function setHttp($http)
{
$this->http = $http;
return $this;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Hanson\Foundation\Exception;
use Exception;
class HttpException extends Exception
{
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Hanson\Foundation;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\FilesystemCache;
use Monolog\Handler\HandlerInterface;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Pimple\Container;
use Symfony\Component\HttpFoundation\Request;
/**
* Class Foundation
* @property-read Http $http
* @package Hanson\Foundation
*/
class Foundation extends Container
{
/**
* an array of service providers.
*
* @var
*/
protected $providers = [];
protected $config;
public function __construct($config)
{
parent::__construct();
$this->setConfig($config);
if ($this->config['debug'] ?? false) {
error_reporting(E_ALL);
}
$this->registerBase();
$this->registerProviders();
$this->initializeLogger();
}
/**
* Register basic providers.
*/
private function registerBase()
{
$this['request'] = function () {
return Request::createFromGlobals();
};
$this['http'] = function () {
return new Http();
};
if ($cache = $this->getConfig()['cache'] ?? null and $cache instanceof Cache) {
$this['cache'] = $this->getConfig()['cache'];
} else {
$this['cache'] = function () {
return new FilesystemCache(sys_get_temp_dir());
};
}
}
/**
* Initialize logger.
*/
private function initializeLogger()
{
if (Log::hasLogger()) {
return;
}
$logger = new Logger($this->getConfig()['log']['name'] ?? 'foundation');
if (!$this->getConfig()['debug'] ?? false || defined('PHPUNIT_RUNNING')) {
$logger->pushHandler(new NullHandler());
} elseif (($this->getConfig()['log']['handler'] ?? null) instanceof HandlerInterface) {
$logger->pushHandler($this->getConfig()['log']['handler']);
} elseif ($logFile = $this->getConfig()['log']['file'] ?? null) {
$logger->pushHandler(new StreamHandler(
$logFile,
$this->getConfig()['log']['level'] ?? Logger::WARNING,
true,
$this->getConfig()['log']['permission'] ?? null
));
}
Log::setLogger($logger);
}
/**
* Register providers.
*/
protected function registerProviders()
{
foreach ($this->providers as $provider) {
$this->register(new $provider());
}
}
public function setConfig(array $config)
{
$this->config = $config;
}
public function getConfig($key = null)
{
return $key ? ($this->config[$key] ?? null) : $this->config;
}
/**
* Magic get access.
*
* @param string $id
*
* @return mixed
*/
public function __get($id)
{
return $this->offsetGet($id);
}
/**
* Magic set access.
*
* @param string $id
* @param mixed $value
*/
public function __set($id, $value)
{
$this->offsetSet($id, $value);
}
public function rebind(string $id, $value)
{
$this->offsetUnset($id);
$this->offsetSet($id, $value);
return $this;
}
}

View File

@@ -0,0 +1,300 @@
<?php
namespace Hanson\Foundation;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\HandlerStack;
use Hanson\Foundation\Exception\HttpException;
use Psr\Http\Message\ResponseInterface;
/**
* Class Http.
*/
class Http
{
/**
* Used to identify handler defined by client code
* Maybe useful in the future.
*/
const USER_DEFINED_HANDLER = 'userDefined';
/**
* Http client.
*
* @var HttpClient
*/
protected $client;
/**
* The middlewares.
*
* @var array
*/
protected $middlewares = [];
protected $stack;
/**
* Guzzle client default settings.
*
* @var array
*/
protected static $defaults = [
'curl' => [
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
],
];
/**
* Set guzzle default settings.
*
* @param array $defaults
*/
public static function setDefaultOptions($defaults = [])
{
self::$defaults = $defaults;
}
/**
* Return current guzzle default settings.
*
* @return array
*/
public static function getDefaultOptions()
{
return self::$defaults;
}
/**
* GET request.
*
* @param string $url
* @param array $options
*
* @return ResponseInterface
*
* @throws HttpException
*/
public function get($url, array $options = [])
{
return $this->request('GET', $url, ['query' => $options]);
}
/**
* POST request.
*
* @param string $url
* @param array $form
*
* @return ResponseInterface
*/
public function post($url, array $form = [])
{
return $this->request('POST', $url, ['form_params' => $form]);
}
/**
* JSON request.
*
* @param string $url
* @param $query
*
* @return ResponseInterface
*/
public function json($url, $query = [])
{
return $this->request('POST', $url, ['json' => $query]);
}
/**
* Upload file.
*
* @param string $url
* @param array $files
* @param array $form
* @param array $queries
*
* @return ResponseInterface
*/
public function upload($url, array $queries = [], array $files = [], array $form = [])
{
$multipart = [];
foreach ($files as $name => $path) {
if (is_array($path)) {
foreach ($path as $item) {
$multipart[] = [
'name' => $name.'[]',
] + $this->fileToMultipart($item);
}
} else {
$multipart[] = [
'name' => $name,
] + $this->fileToMultipart($path);
}
}
foreach ($form as $name => $contents) {
$multipart = array_merge($multipart, $this->normalizeMultipartField($name, $contents));
}
return $this->request('POST', $url, ['query' => $queries, 'multipart' => $multipart]);
}
/**
* @param string $name
* @param mixed $contents
*
* @return array
*/
public function normalizeMultipartField(string $name, $contents)
{
$field = [];
if (!is_array($contents)) {
return [compact('name', 'contents')];
} else {
foreach ($contents as $key => $value) {
$key = sprintf('%s[%s]', $name, $key);
$field = array_merge($field, is_array($value) ? $this->normalizeMultipartField($key, $value) : [
[
'name' => $key, 'contents' => $value
]
]);
}
}
return $field;
}
private function fileToMultipart($file)
{
if (is_array($file)) {
return $file;
} elseif (@file_exists($file)) {
return ['contents' => fopen($file, 'r')];
} elseif (filter_var($file, FILTER_VALIDATE_URL)) {
return ['contents' => file_get_contents($file)];
} else {
return ['contents' => $file];
}
}
/**
* Set GuzzleHttp\Client.
*
* @param \GuzzleHttp\Client $client
*
* @return Http
*/
public function setClient(HttpClient $client)
{
$this->client = $client;
return $this;
}
/**
* Return GuzzleHttp\Client instance.
*
* @return \GuzzleHttp\Client
*/
public function getClient()
{
if (!($this->client instanceof HttpClient)) {
$this->client = new HttpClient();
}
return $this->client;
}
/**
* Add a middleware.
*
* @param callable $middleware
*
* @return $this
*/
public function addMiddleware(callable $middleware)
{
array_push($this->middlewares, $middleware);
return $this;
}
/**
* Return all middlewares.
*
* @return array
*/
public function getMiddlewares()
{
return $this->middlewares;
}
/**
* Make a request.
*
* @param string $url
* @param string $method
* @param array $options
*
* @return ResponseInterface
*/
public function request($method, $url, $options = [])
{
$method = strtoupper($method);
$options = array_merge(self::$defaults, $options);
Log::debug('Client Request:', compact('url', 'method', 'options'));
$options['handler'] = $this->getHandler();
$response = $this->getClient()->request($method, $url, $options);
Log::debug('API response:', [
'Status' => $response->getStatusCode(),
'Reason' => $response->getReasonPhrase(),
'Headers' => $response->getHeaders(),
'Body' => strval($response->getBody()),
]);
return $response;
}
/**
* Build a handler.
*
* @return HandlerStack
*/
public function getHandler()
{
if ($this->stack) {
return $this->stack;
}
$stack = HandlerStack::create();
foreach ($this->middlewares as $middleware) {
$stack->push($middleware);
}
if (isset(static::$defaults['handler']) && is_callable(static::$defaults['handler'])) {
$stack->push(static::$defaults['handler'], self::USER_DEFINED_HANDLER);
}
$this->stack = $stack;
return $stack;
}
public function addHandler($guzzleHandler)
{
$stack = $this->getHandler();
$stack->setHandler(is_string($guzzleHandler) ? new $guzzleHandler() : $guzzleHandler);
$this->stack = $stack;
return $this;
}
}

104
vendor/hanson/foundation-sdk/src/Log.php vendored Normal file
View File

@@ -0,0 +1,104 @@
<?php
namespace Hanson\Foundation;
use Monolog\Handler\ErrorLogHandler;
use Monolog\Handler\NullHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
/**
* Class Log.
*
* @method static debug($message, $context = null)
* @method static info($message, $context = null)
* @method static notice($message, $context = null)
* @method static warning($message, $context = null)
* @method static error($message, $context = null)
* @method static critical($message, $context = null)
* @method static alert($message, $context = null)
* @method static emergency($message, $context = null)
*/
class Log
{
/**
* Logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected static $logger;
/**
* Return the logger instance.
*
* @return \Psr\Log\LoggerInterface
*/
public static function getLogger()
{
return self::$logger ?: self::$logger = self::createDefaultLogger();
}
/**
* Set logger.
*
* @param \Psr\Log\LoggerInterface $logger
*/
public static function setLogger(LoggerInterface $logger)
{
self::$logger = $logger;
}
/**
* Tests if logger exists.
*
* @return bool
*/
public static function hasLogger()
{
return self::$logger ? true : false;
}
/**
* Forward call.
*
* @param string $method
* @param array $args
*
* @return mixed
*/
public static function __callStatic($method, $args)
{
return forward_static_call_array([self::getLogger(), $method], $args);
}
/**
* Forward call.
*
* @param string $method
* @param array $args
*
* @return mixed
*/
public function __call($method, $args)
{
return call_user_func_array([self::getLogger(), $method], $args);
}
/**
* Make a default log instance.
*
* @return \Monolog\Logger
*/
private static function createDefaultLogger()
{
$log = new Logger('Foundation');
if (defined('PHPUNIT_RUNNING') || php_sapi_name() === 'cli') {
$log->pushHandler(new NullHandler());
} else {
$log->pushHandler(new ErrorLogHandler());
}
return $log;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Hanson\Foundation;
use GuzzleHttp\Handler\MockHandler;
use PHPUnit\Framework\TestCase;
class HttpTest extends TestCase
{
public function testAddHandler()
{
$http = new Http();
$stack = $http->addHandler(new MockHandler());
var_dump($stack);
}
}