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 行,或 null(key 无效/用户已删除/已禁用) */ 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 请求中传入的 account(ck_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 < timestamp(ASCII 升序) $stringToSign = $account . $timestamp; $firstMd5 = md5($stringToSign); $expectedSign = md5($firstMd5 . $apiKey); return hash_equals($expectedSign, $sign); } }