PHP 示例
本章提供完整可运行的 PHP 代码,所有函数都已通过单元测试。可以直接 require 到你的项目中使用。
1. 独立授权类(无 WordPress 依赖)
适用于:纯 PHP 项目、ThinkPHP、Laravel 等。
<?php
/**
* Shiguang License Client
*
* 独立的授权客户端类,可用于任何 PHP 项目
* 要求:PHP 7.2+,需要 cURL 扩展
*
* @version 1.0.0
* @license MIT
*/
class ShiguangLicenseClient {
private $server_url;
private $product_slug;
private $api_secret;
public function __construct($server_url, $product_slug, $api_secret) {
$this->server_url = rtrim($server_url, '/');
$this->product_slug = $product_slug;
$this->api_secret = $api_secret;
}
/**
* 标准化域名
*/
public static function normalizeDomain($input) {
$domain = strtolower(trim($input));
$domain = preg_replace('#^https?://#', '', $domain);
if (($pos = strpos($domain, '/')) !== false) {
$domain = substr($domain, 0, $pos);
}
if (($pos = strpos($domain, ':')) !== false) {
$domain = substr($domain, 0, $pos);
}
$domain = preg_replace('#^www\.#', '', $domain);
return $domain;
}
/**
* 生成 API 签名
*/
private function sign($domain, $license_key, $serial = 1) {
$sign_data = $domain . '|' . $license_key . '|' . $this->product_slug . '|' . $serial;
return hash_hmac('sha256', $sign_data, $this->api_secret);
}
/**
* 在线验证授权
*/
public function verifyOnline($license_key, $domain = null) {
if ($domain === null) {
$domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
}
$domain = self::normalizeDomain($domain);
$serial = 1;
$signature = $this->sign($domain, $license_key, $serial);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $this->server_url . '/api/verify.php',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'license_key' => $license_key,
'domain' => $domain,
'product' => $this->product_slug,
'serial' => $serial,
'signature' => $signature,
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) {
return ['status' => 'error', 'message' => '网络错误:' . $error];
}
$data = json_decode($response, true);
if (!$data) {
return ['status' => 'error', 'message' => '服务器返回无效响应 (HTTP ' . $http_code . ')'];
}
return $data;
}
/**
* 带本地缓存的授权检查(缓存 7 天)
*
* @param string $license_key 授权码
* @param string|null $domain 域名(默认当前访问域名)
* @param int $cache_days 缓存天数(默认 7)
* @param string|null $cache_file 缓存文件路径(默认 /tmp/shiguang_license.cache)
* @return array 授权信息
*/
public function checkLicense($license_key, $domain = null, $cache_days = 7, $cache_file = null) {
if ($cache_file === null) {
$cache_file = sys_get_temp_dir() . '/shiguang_license_' . md5($license_key) . '.cache';
}
// 读取缓存
if (file_exists($cache_file)) {
$cached = @json_decode(file_get_contents($cache_file), true);
if ($cached && isset($cached['last_check']) && isset($cached['info'])) {
$age = time() - strtotime($cached['last_check']);
if ($age < $cache_days * 86400 && ($cached['info']['status'] ?? '') === 'active') {
// 缓存有效,但本地再校验一次域名
$current = self::normalizeDomain($domain ?? ($_SERVER['HTTP_HOST'] ?? ''));
if (($cached['info']['domain'] ?? '') === $current) {
return $cached['info'];
}
}
}
}
// 在线验证
$result = $this->verifyOnline($license_key, $domain);
if (($result['status'] ?? '') === 'success') {
$info = [
'status' => 'active',
'license_key' => $license_key,
'domain' => $result['license']['domain'],
'type' => $result['license']['type'],
'expires_at' => $result['license']['expires_at'],
'max_sites' => $result['license']['max_sites'],
'last_check' => date('Y-m-d H:i:s'),
];
// 写缓存
@file_put_contents($cache_file, json_encode([
'last_check' => $info['last_check'],
'info' => $info,
]));
return $info;
}
return ['status' => 'error', 'message' => $result['message'] ?? '验证失败'];
}
/**
* 仅检查授权是否有效(快捷方法)
*/
public function isLicensed($license_key, $domain = null) {
$info = $this->checkLicense($license_key, $domain);
return ($info['status'] ?? '') === 'active';
}
}
2. 使用示例
基本用法
<?php
require_once 'ShiguangLicenseClient.php';
$client = new ShiguangLicenseClient(
'https://license.example.com', // 授权服务器地址
'my-awesome-plugin', // 产品 slug
'your-api-secret-here' // API 密钥
);
// 检查授权
$license_key = 'LIC-9C20E-C6A64-B6DE1-D4F90';
if ($client->isLicensed($license_key)) {
echo "授权有效!\n";
} else {
echo "授权无效\n";
}
// 获取详细信息
$info = $client->checkLicense($license_key);
print_r($info);
// 输出:
// Array (
// [status] => active
// [license_key] => LIC-9C20E-C6A64-B6DE1-D4F90
// [domain] => example.com
// [type] => standard
// [expires_at] => // null 表示永久
// [max_sites] => 1
// [last_check] => 2026-07-22 14:30:12
// )
作为中间件使用
<?php
// 中间件:验证所有需要授权的 API 调用
function require_license_or_die($license_key) {
$client = new ShiguangLicenseClient(
getenv('LICENSE_SERVER'),
getenv('PRODUCT_SLUG'),
getenv('API_SECRET')
);
if (!$client->isLicensed($license_key)) {
http_response_code(403);
echo json_encode(['error' => '授权无效,请购买后使用']);
exit;
}
}
// 在敏感 API 入口调用
if ($_SERVER['REQUEST_URI'] === '/api/premium-feature') {
require_license_or_die($_POST['license_key'] ?? '');
}
3. 单元测试示例
用 PHPUnit 测试授权客户端:
<?php
// tests/ShiguangLicenseClientTest.php
use PHPUnit\Framework\TestCase;
class ShiguangLicenseClientTest extends TestCase {
private $client;
private $test_license_key = 'LIC-TEST1-TEST2-TEST3-TEST4';
private $test_domain = 'test.example.com';
protected function setUp(): void {
$this->client = new ShiguangLicenseClient(
getenv('LICENSE_SERVER') ?: 'https://license.example.com',
getenv('PRODUCT_SLUG') ?: 'test-product',
getenv('API_SECRET') ?: 'test-secret'
);
}
public function testNormalizeDomain() {
$this->assertEquals(
'example.com',
ShiguangLicenseClient::normalizeDomain('HTTPS://WWW.Example.COM/path')
);
$this->assertEquals(
'example.com',
ShiguangLicenseClient::normalizeDomain('example.com:8080')
);
$this->assertEquals(
'localhost',
ShiguangLicenseClient::normalizeDomain('http://localhost/wp-admin')
);
}
public function testVerifyOnlineWithValidKey() {
$result = $this->client->verifyOnline($this->test_license_key, $this->test_domain);
$this->assertEquals('success', $result['status']);
$this->assertArrayHasKey('license', $result);
}
public function testVerifyOnlineWithInvalidKey() {
$result = $this->client->verifyOnline('LIC-INVALID-INVALID-INVALID-INVALID', $this->test_domain);
$this->assertNotEquals('success', $result['status']);
}
public function testCheckLicenseWithCache() {
// 第一次:在线验证
$start = microtime(true);
$info1 = $this->client->checkLicense($this->test_license_key, $this->test_domain, 7, '/tmp/test_license.cache');
$online_time = microtime(true) - $start;
$this->assertEquals('active', $info1['status']);
// 第二次:应该命中缓存(< 1ms)
$start = microtime(true);
$info2 = $this->client->checkLicense($this->test_license_key, $this->domain ?? $this->test_domain, 7, '/tmp/test_license.cache');
$cache_time = microtime(true) - $start;
$this->assertEquals('active', $info2['status']);
$this->assertLessThan(0.01, $cache_time); // 缓存命中 < 10ms
$this->assertGreaterThan($cache_time * 10, $online_time); // 在线验证至少慢 10 倍
}
}
运行测试:
LICENSE_SERVER=https://license.example.com \
PRODUCT_SLUG=test-product \
API_SECRET=test-secret \
vendor/bin/phpunit tests/ShiguangLicenseClientTest.php
4. Composer 包配置
如果你想发布为 Composer 包:
{
"name": "shiguang/license-client",
"description": "时光授权系统 PHP 客户端",
"type": "library",
"license": "MIT",
"require": {
"php": ">=7.2",
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*"
},
"autoload": {
"psr-4": {
"Shiguang\\License\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Shiguang\\License\\Tests\\": "tests/"
}
}
}
然后在你的项目中使用:
composer require shiguang/license-client
<?php
require_once 'vendor/autoload.php';
use Shiguang\License\ShiguangLicenseClient;
$client = new ShiguangLicenseClient(
'https://license.example.com',
'my-plugin',
'secret'
);
if ($client->isLicensed('LIC-XXXXX-XXXXX-XXXXX-XXXXX')) {
// 业务逻辑
}
5. 常见错误处理
| 错误 | 原因 | 解决 |
|---|---|---|
签名错误 |
api_secret 与服务器端不一致 |
联系授权方核对密钥 |
授权不存在 |
授权码拼写错误或不存在 | 从用户中心复制授权码 |
域名不匹配 |
购买时的域名与实际访问域名不同 | 在用户中心修改域名 |
授权已暂停 |
管理员暂停了授权 | 联系客服 |
授权已过期 |
超过 expires_at | 续费 |
网络错误 |
无法连接授权服务器 | 检查服务器网络、防火墙 |
下一步:阅读 API 参考 → 授权验证接口,了解所有可用的 API 端点。