From ac4d34884e92c7ddb822b431420dfd64b99a1579 Mon Sep 17 00:00:00 2001 From: augushong Date: Mon, 27 Apr 2026 00:07:18 +0800 Subject: [PATCH] feat(api-key): add api_key table, source fields, model and auth middleware --- app/api/middleware.php | 4 + app/middleware/ApiKeyAuth.php | 42 ++++++ app/model/ApiKey.php | 138 ++++++++++++++++++ .../20260426235556_create_table_api_key.php | 47 ++++++ ...57_add_source_to_post_and_upload_files.php | 39 +++++ 5 files changed, 270 insertions(+) create mode 100644 app/api/middleware.php create mode 100644 app/middleware/ApiKeyAuth.php create mode 100644 app/model/ApiKey.php create mode 100644 database/migrations/20260426235556_create_table_api_key.php create mode 100644 database/migrations/20260426235557_add_source_to_post_and_upload_files.php diff --git a/app/api/middleware.php b/app/api/middleware.php new file mode 100644 index 0000000..cd7c514 --- /dev/null +++ b/app/api/middleware.php @@ -0,0 +1,4 @@ +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); + } +} diff --git a/app/model/ApiKey.php b/app/model/ApiKey.php new file mode 100644 index 0000000..05cf1aa --- /dev/null +++ b/app/model/ApiKey.php @@ -0,0 +1,138 @@ +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; + } +} diff --git a/database/migrations/20260426235556_create_table_api_key.php b/database/migrations/20260426235556_create_table_api_key.php new file mode 100644 index 0000000..8a4bb0b --- /dev/null +++ b/database/migrations/20260426235556_create_table_api_key.php @@ -0,0 +1,47 @@ +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(); + } +} diff --git a/database/migrations/20260426235557_add_source_to_post_and_upload_files.php b/database/migrations/20260426235557_add_source_to_post_and_upload_files.php new file mode 100644 index 0000000..cb2ded1 --- /dev/null +++ b/database/migrations/20260426235557_add_source_to_post_and_upload_files.php @@ -0,0 +1,39 @@ +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(); + } +}