Files
CKB-Interface/application/common/service/UserApiKeyService.php
2026-03-24 10:39:16 +08:00

154 lines
4.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\service;
use think\Db;
use think\facade\Log;
/**
* 用户 API Key 服务
*
* Key 格式与"场景获客"保持一致:
* 5 组 × 5 位(大小写字母 + 数字),组间用 "-" 连接
* 示例aB3k9-Z8c1Q-0f4Xk-M9n2P-1A2b3
*
* 绑定对象ck_users 表(`users`)中的 `apiKey` 字段
*/
class UserApiKeyService
{
/** 字符集:大小写字母 + 数字 */
private static $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
/**
* 生成一个在 ck_users 中唯一的 API Key
*
* @return string 格式xxxxx-xxxxx-xxxxx-xxxxx-xxxxx
*/
public static function generate(): string
{
$chars = self::$chars;
$charLen = strlen($chars);
while (true) {
$key = '';
for ($i = 0; $i < 5; $i++) {
$segment = '';
for ($j = 0; $j < 5; $j++) {
$segment .= $chars[mt_rand(0, $charLen - 1)];
}
$key .= ($i > 0 ? '-' : '') . $segment;
}
// 确保全局唯一
$exists = Db::name('users')->where('apiKey', $key)->find();
if (!$exists) {
return $key;
}
}
}
/**
* 为指定用户绑定 API Key幂等已有则直接返回没有则生成并写入
*
* @param int $userId ck_users.id
* @return string 该用户的 apiKey
* @throws \RuntimeException
*/
public static function bindOrGet(int $userId): string
{
$user = Db::name('users')
->where('id', $userId)
->where('deleteTime', 0)
->field('id, apiKey')
->find();
if (!$user) {
throw new \RuntimeException('用户不存在');
}
if (!empty($user['apiKey'])) {
return $user['apiKey'];
}
return self::forceGenerate($userId);
}
/**
* 强制为指定用户重新生成 API Key会覆盖旧 Key
*
* @param int $userId ck_users.id
* @return string 新生成的 apiKey
* @throws \RuntimeException
*/
public static function forceGenerate(int $userId): string
{
$user = Db::name('users')
->where('id', $userId)
->where('deleteTime', 0)
->field('id')
->find();
if (!$user) {
throw new \RuntimeException('用户不存在');
}
$apiKey = self::generate();
Db::name('users')
->where('id', $userId)
->update([
'apiKey' => $apiKey,
'updateTime' => time(),
]);
Log::info("UserApiKeyService: 用户 #{$userId} 生成/更新 apiKey");
return $apiKey;
}
/**
* 通过 apiKey 查找用户(用于对外接口身份校验)
*
* @param string $apiKey
* @return array|null ck_users 行,或 nullkey 无效/用户已删除/已禁用)
*/
public static function findUserByKey(string $apiKey): ?array
{
if (empty($apiKey)) {
return null;
}
$user = Db::name('users')
->where('apiKey', $apiKey)
->where('deleteTime', 0)
->where('status', 1)
->find();
return $user ?: null;
}
/**
* 验证签名
*
* 只有三个固定字段参与签名account、timestamp、apiKey
* stringToSign = account + timestamp (按字段名 ASCII 升序拼接值)
* firstMd5 = MD5(stringToSign)
* sign = MD5(firstMd5 + apiKey)
*
* @param string $account 请求中传入的 accountck_users.account
* @param string $timestamp 请求中传入的 timestamp
* @param string $apiKey 用户 apiKey
* @param string $sign 客户端传来的签名
* @return bool
*/
public static function validateSign(string $account, string $timestamp, string $apiKey, string $sign): bool
{
// account < timestampASCII 升序)
$stringToSign = $account . $timestamp;
$firstMd5 = md5($stringToSign);
$expectedSign = md5($firstMd5 . $apiKey);
return hash_equals($expectedSign, $sign);
}
}