主题
Openapi
openapi
模块提供了平台接口的开放能力。如果后台接口需要曝露给外部使用,但又不想给对方创建用户。那么这个模块可以很好实现这个能力
数据结构
openapi_users
用户表openapi_user_balance
用户余额表openapi_request_log
请求日志表
php
Schema::create('openapi_users', function (Blueprint $table) {
$table->id()->comment('ID');
$table->string('username')->comment('用户名');
$table->string('mobile', 20)->comment('手机号');
$table->string('password')->comment('密码');
$table->string('company')->nullable()->comment('公司名称');
$table->string('description')->nullable()->comment('描述');
$table->unsignedInteger('qps')->default(100)->comment('每分钟的 QPS');
$table->string('app_key')->comment('app key');
$table->string('app_secret')->comment('密钥');
$table->creatorId();
$table->createdAt();
$table->updatedAt();
$table->deletedAt();
$table->engine = 'InnoDB';
$table->comment('openapi 用户表');
});
Schema::create('openapi_user_balance', function (Blueprint $table) {
$table->id();
$table->uuid();
$table->integer('user_id')->comment('用户id');
$table->unsignedInteger('balance')->default(0)->comment('用户余额');
$table->createdAt();
$table->updatedAt();
$table->deletedAt();
$table->engine = 'InnoDB';
$table->comment('用户余额表');
});
Schema::create('openapi_request_log', function (Blueprint $table) {
$table->id();
$table->uuid('request_id')->comment('请求id');
$table->string('app_key')->comment('app key');
$table->json('data')->comment('请求数据');
$table->createdAt();
$table->updatedAt();
$table->engine = 'InnoDB';
$table->comment('openapi 请求日志');
});
创建 openapi 用户
使用管理后台的 openapi 模块来创建用户
QPS
字段限制了访问接口频率,目前这个字段是设置每分钟的接口频率
在创建成功之后,会分配给用户一个 AppKey
和 AppSecret
。第三方用户可以使用他来接入
接入
想要使用openapi
的功能,接入验签和速率限制都非常简单。一个简单的示例,在根目录 routes/api.php
添加如下路由代码
php
Route::prefix('v1')->middleware([
\Modules\Openapi\Middlewares\CheckSignatureMiddleware::class
])->group(function () {
Route::get('user', function (){
return \Modules\Openapi\Facade\OpenapiResponse::success([]);
});
});
在浏览器打开 域名/api/v1/user
链接,会出现如下响应,就说明已经成功了
快速验证
如果你使用 apifox
软件的话,可以添加一个前置脚本快速验证下 脚本内容如下
javascript
const appKey = pm.environment.get('app_key')
const appSecret = pm.environment.get('app_secret')
console.log(appSecret, appKey)
function createSign(params) {
const keys = Object.keys(params).sort()
const signStr = keys.map((key) => `${key}=${params[key]}`).join('&')
return CryptoJS.HmacSHA256(signStr, appSecret).toString()
}
let params = {}
params['timestamp'] = Math.floor(Date.now() / 1000)
if (pm.request.method == 'GET') {
queryString = pm.request.url.getQueryString()
if (queryString) {
pm.request.url
.getQueryString()
.split('&')
.forEach((item) => {
items = item.split('=')
params[items[0]] = items[1]
})
}
const signature = createSign(params)
pm.request.headers.add({ key: 'app-key', value: appKey })
pm.request.headers.add({ key: 'signature', value: signature })
let query = ''
for (let key in params) {
query += key + '=' + params[key] + '&'
}
pm.request.url.query = query
} else {
if (pm.request.body.mode == 'urlencoded') {
pm.request.body.urlencoded.each((item) => {
params[item.key] = item.value
})
const signature = createSign(params)
pm.request.headers.add({ key: 'app-key', value: appKey })
pm.request.headers.add({ key: 'signature', value: signature })
pm.request.body.urlencoded.add({
disabled: false,
key: 'timestamp',
value: params.timestamp
})
}
if (pm.request.body.mode == 'formdata') {
}
}
然后给选定的环境配置 appkey
和 appsecret
, 如下
通过apifox
发送请求,出现如下结果,就说明成功了
中间件
- 验签中间件
\Modules\Openapi\Middlewares\CheckSignatureMiddleware::class
- 速率(QPS)限制中间件
\Modules\Openapi\Middlewares\RateLimiterMiddleware::class
异常
openapi
模块的所有异常都需要继承 OpenapiException
,例如非法的 AppKey
php
namespace Modules\Openapi\Exceptions;
use Modules\Openapi\Enums\Code;
// 不合法的 app key
class InvalidAppKeyException extends OpenapiException
{
protected $code = Code::INVALID_APP_KEY;
}
找到 bootstrap/app.php
,可以找到对应的 openapi exception
渲染。代码如下
php
$app = Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
// 这里就是用来处理 openapi exception
// 其他类型自定义异常请自行处理
// 系统异常请自行处理
$exceptions->render(function (OpenapiException $exception, Request $request) {
return OpenapiResponse::error($exception->getMessage(), $exception->getCode());
});
})->create();
Code 枚举
所有枚举都要继承 Modules\Openapi\Enums\Enum
接口
php
namespace Modules\Openapi\Enums;
enum Code: int implements Enum
{
case SUCCESS = 10000; // 成功的code
case FAILED = 10001; // 失败的 code
case APP_KEY_LOST = 10002; // app key 失效
case SIGNATURE_LOST = 10003; // 签名失效
case INVALID_APP_KEY = 10004; // 无效app key
case INVALID_SIGNATURE = 10005; // 无效签名
case INVALID_TIMESTAMP = 10006; // 无效时间
case Balance_NOT_ENOUGH = 10007; // 余额不足
case RATE_LIMIT = 10008; // 限流
public function name(): string
{
return match ($this) {
self::SUCCESS => 'success',
self::FAILED => 'failed',
self::APP_KEY_LOST => 'app key 丢失',
self::SIGNATURE_LOST => 'signature 丢失',
self::INVALID_APP_KEY => '无效 app key',
self::INVALID_SIGNATURE => '无效签名',
self::INVALID_TIMESTAMP => '无效 timestamp',
self::Balance_NOT_ENOUGH => '余额不足',
self::RATE_LIMIT => '请求过于频繁'
};
}
// 自定义 code
/**
* @param mixed $value
* @return bool
*/
public function equal(mixed $value): bool
{
return $this->value == $value;
}
}