feat(api-key): add api_key table, source fields, model and auth middleware

This commit is contained in:
augushong
2026-04-27 00:07:18 +08:00
parent 351808bf07
commit ac4d34884e
5 changed files with 270 additions and 0 deletions

4
app/api/middleware.php Normal file
View File

@@ -0,0 +1,4 @@
<?php
// API中间件通过控制器 $middleware 属性挂载,不需要全局注册
return [];

View File

@@ -0,0 +1,42 @@
<?php
namespace app\middleware;
use app\model\ApiKey;
class ApiKeyAuth
{
public function handle($request, \Closure $next)
{
$raw_key = '';
// 优先从 Authorization: Bearer {key} 提取
$authorization = $request->header('authorization', '');
if (strpos($authorization, 'Bearer ') === 0) {
$raw_key = substr($authorization, 7);
}
// 若无 Bearer尝试从 X-API-Key 请求头获取
if (empty($raw_key)) {
$raw_key = $request->header('x-api-key', '');
}
if (empty($raw_key)) {
return json(['code' => 401, 'msg' => '缺少 API Key', 'data' => null])->code(401);
}
$api_key = ApiKey::verifyKey($raw_key);
if (empty($api_key)) {
return json(['code' => 401, 'msg' => 'API Key 无效或已禁用', 'data' => null])->code(401);
}
// 注入权限到 Request
$request->admin_id = $api_key->admin_id;
$request->api_key_id = $api_key->id;
$request->can_write_own = $api_key->can_write_own;
$request->can_write_other = $api_key->can_write_other;
$request->can_delete = $api_key->can_delete;
return $next($request);
}
}

138
app/model/ApiKey.php Normal file
View File

@@ -0,0 +1,138 @@
<?php
namespace app\model;
use app\common\model\Base;
use think\model\concern\SoftDelete;
/**
* @mixin think\Model
*/
class ApiKey extends Base
{
use SoftDelete;
protected $name = 'api_key';
protected $defaultSoftDelete = 0;
public function admin()
{
return $this->belongsTo(Admin::class, 'admin_id');
}
/**
* 生成API Key
*
* @param int $admin_id
* @param string $name
* @param int $can_write_own
* @param int $can_write_other
* @param int $can_delete
*
* @return string 明文Key仅此一次返回
*/
public static function generateKey($admin_id, $name = '', $can_write_own = 0, $can_write_other = 0, $can_delete = 0)
{
$raw_key = 'ak_' . bin2hex(random_bytes(16));
$hash = md5($raw_key);
self::create([
'admin_id' => $admin_id,
'name' => $name,
'api_key' => $hash,
'can_write_own' => $can_write_own,
'can_write_other' => $can_write_other,
'can_delete' => $can_delete,
'status' => 1,
]);
return $raw_key;
}
/**
* 验证API Key
*
* @param string $raw_key
*
* @return static|null
*/
public static function verifyKey($raw_key)
{
$hash = md5($raw_key);
return self::where('api_key', $hash)->where('status', 1)->find();
}
/**
* 重新生成Key
*
* @param int $id
*
* @return string 新的明文Key
*/
public static function regenerateKey($id)
{
$record = self::find($id);
if (empty($record)) {
return '';
}
$raw_key = 'ak_' . bin2hex(random_bytes(16));
$hash = md5($raw_key);
$record->api_key = $hash;
$record->save();
return $raw_key;
}
/**
* 是否可写自己的文章
*
* @return bool
*/
public function canWriteOwn()
{
return $this->can_write_own == 1;
}
/**
* 是否可写他人的文章
*
* @return bool
*/
public function canWriteOther()
{
return $this->can_write_other == 1;
}
/**
* 是否可删除
* can_delete=0: 不可删除
* can_delete=1: 仅API来源可删除
* can_delete=2: 可删除
*
* @param string|null $source
*
* @return bool
*/
public function canDelete($source = null)
{
$can_delete = (int) $this->can_delete;
if ($can_delete === 0) {
return false;
}
if ($can_delete === 1) {
return $source === 'api';
}
if ($can_delete === 2) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,47 @@
<?php
use app\common\ColumnFormat;
use think\migration\Migrator;
use think\migration\db\Column;
class CreateTableApiKey extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('api_key', ['comment' => 'API密钥表', 'signed' => false]);
$table->addColumn('admin_id', 'integer', ['limit' => 10, 'signed' => false, 'comment' => '关联管理员ID']);
$table->addColumn('api_key', 'string', ['limit' => 64, 'comment' => 'API密钥(md5哈希)']);
$table->addColumn(ColumnFormat::stringNormal('name')->setComment('API Key名称/备注'));
$table->addColumn(ColumnFormat::integerTypeStatus('status', 1)->setComment('状态,0:禁用,1:启用'));
$table->addColumn(ColumnFormat::integerTypeStatus('can_write_own')->setComment('可写自己的,0:不可,1:可'));
$table->addColumn(ColumnFormat::integerTypeStatus('can_write_other')->setComment('可写其他的,0:不可,1:可'));
$table->addColumn(ColumnFormat::integerTypeStatus('can_delete')->setComment('删除权限,0:不可删除,1:仅删API数据,2:可删所有'));
$table->addColumn(ColumnFormat::timestamp('create_time'));
$table->addColumn(ColumnFormat::timestamp('update_time'));
$table->addColumn(ColumnFormat::timestamp('delete_time'));
$table->addIndex('admin_id');
$table->addIndex('api_key', ['unique' => true]);
$table->create();
}
}

View File

@@ -0,0 +1,39 @@
<?php
use app\common\ColumnFormat;
use think\migration\Migrator;
class AddSourceToPostAndUploadFiles extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$post = $this->table('post');
$post->addColumn(ColumnFormat::stringShort('source')->setDefault('admin')->setComment('来源'))
->update();
$uploadFiles = $this->table('upload_files');
$uploadFiles->addColumn(ColumnFormat::stringShort('source')->setDefault('admin')->setComment('来源'))
->update();
}
}