一、接口信息

接口地址:https://您的域名/index.php/auth/接口方法

请求方式:POST,请求体为JSON格式

请求头:Content-Type: application/json

接口列表

接口说明
POST /auth/verify验证卡密状态
POST /auth/activate激活卡密(登录)
POST /auth/heartbeat心跳检测(60-120秒调用一次)
POST /auth/logout登出

二、签名算法

⚠️ 注意事项

1. 参数按key升序排列(字母顺序)

2. 空值和null不参与签名

3. 每个参数后加 & 符号,最后追加 appkey=您的AppKey

4. HMAC-SHA256签名后转小写

function generateSign($params, $appkey) { unset($params['sign']); ksort($params); $str = ''; foreach ($params as $k => $v) { if ($v !== '' && $v !== null) { $str .= $k . '=' . $v . '&'; } } $str .= 'appkey=' . $appkey; return strtolower(hash_hmac('sha256', $str, $appkey)); }
// 需引入 CryptoJS function generateSign(params, appkey) { const sortedKeys = Object.keys(params).sort(); let str = ''; sortedKeys.forEach(key => { if (key !== 'sign' && params[key] !== '' && params[key] !== null && params[key] !== undefined) { str += key + '=' + params[key] + '&'; } }); str += 'appkey=' + appkey; return CryptoJS.HmacSHA256(str, appkey).toString().toLowerCase(); }

三、请求参数

参数类型必填说明
appidstring接口AppID
card_codestring卡密
device_codestring设备码(开启设备码绑定时必填)
timestampint当前时间戳(秒)
noncestring随机字符串
signstring签名

四、返回数据

字段类型说明
statusint卡密状态:0=未激活,1=已激活
auth_modeint授权模式:0=关闭(无需验证),1=开启(需要卡密验证)
expire_timeint到期时间戳,0=永久
remaining_daysint剩余天数,-1=永久,0=已过期
is_permanentbool是否永久卡密
device_bind_enabledbool是否开启设备码绑定

授权模式说明

🔐 灵活授权模式

auth_mode = 0(授权模式关闭):接口返回成功状态,对接项目可直接跳过授权框进入主程序,无需卡密验证。

auth_mode = 1(授权模式开启):需要正常验证卡密,对接项目应显示授权框要求用户输入卡密。

对接项目应根据 auth_mode 字段动态控制是否显示授权框!

错误码

错误码说明
1001缺少卡密
1002卡密不存在
1003签名验证失败
1004卡密已过期
1005卡密已冻结
1006设备码不匹配

五、对接代码示例

<?php class HjAuth { private $apiUrl, $appid, $appkey; public function __construct($apiUrl, $appid, $appkey) { $this->apiUrl = rtrim($apiUrl, '/'); $this->appid = $appid; $this->appkey = $appkey; } private function generateSign($params) { ksort($params); $str = ''; foreach ($params as $k => $v) { if ($v !== '' && $v !== null) { $str .= $k . '=' . $v . '&'; } } $str .= 'appkey=' . $this->appkey; return strtolower(hash_hmac('sha256', $str, $this->appkey)); } private function request($action, $cardCode, $deviceCode = null) { $params = [ 'appid' => $this->appid, 'card_code' => $cardCode, 'timestamp' => time(), 'nonce' => md5(uniqid()) ]; if ($deviceCode) $params['device_code'] = $deviceCode; $params['sign'] = $this->generateSign($params); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->apiUrl . '/' . $action); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); $result = curl_exec($ch); curl_close($ch); return json_decode($result, true); } public function verify($cardCode, $deviceCode = null) { return $this->request('verify', $cardCode, $deviceCode); } public function activate($cardCode, $deviceCode = null) { return $this->request('activate', $cardCode, $deviceCode); } public function heartbeat($cardCode, $deviceCode = null) { return $this->request('heartbeat', $cardCode, $deviceCode); } public function logout($cardCode, $deviceCode = null) { return $this->request('logout', $cardCode, $deviceCode); } }
<script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> <script> class HjAuth { constructor(apiUrl, appid, appkey) { this.apiUrl = apiUrl.replace(/\/$/, ''); this.appid = appid; this.appkey = appkey; } generateSign(params) { const sortedKeys = Object.keys(params).sort(); let str = ''; sortedKeys.forEach(key => { if (key !== 'sign' && params[key] !== '' && params[key] !== null && params[key] !== undefined) { str += key + '=' + params[key] + '&'; } }); str += 'appkey=' + this.appkey; return CryptoJS.HmacSHA256(str, this.appkey).toString().toLowerCase(); } async request(action, cardCode, deviceCode) { const params = { appid: this.appid, card_code: cardCode, timestamp: Math.floor(Date.now() / 1000), nonce: Math.random().toString(36).substring(2) }; if (deviceCode) params.device_code = deviceCode; params.sign = this.generateSign(params); const response = await fetch(this.apiUrl + '/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }); return await response.json(); } verify(cardCode, deviceCode) { return this.request('verify', cardCode, deviceCode); } activate(cardCode, deviceCode) { return this.request('activate', cardCode, deviceCode); } heartbeat(cardCode, deviceCode) { return this.request('heartbeat', cardCode, deviceCode); } logout(cardCode, deviceCode) { return this.request('logout', cardCode, deviceCode); } } </script>
// npm install @capacitor/device @capacitor/preferences crypto-js import { Device } from '@capacitor/device'; import { Preferences } from '@capacitor/preferences'; import CryptoJS from 'crypto-js'; class HjAuthApp { constructor(apiUrl, appid, appkey) { this.apiUrl = apiUrl; this.appid = appid; this.appkey = appkey; } async getDeviceCode() { const stored = await Preferences.get({ key: 'device_code' }); if (stored.value) return stored.value; const info = await Device.getId(); const deviceCode = 'CAP_' + info.identifier; await Preferences.set({ key: 'device_code', value: deviceCode }); return deviceCode; } generateSign(params) { const sortedKeys = Object.keys(params).sort(); let str = ''; sortedKeys.forEach(key => { if (key !== 'sign' && params[key] !== '' && params[key] !== null && params[key] !== undefined) { str += key + '=' + params[key] + '&'; } }); str += 'appkey=' + this.appkey; return CryptoJS.HmacSHA256(str, this.appkey).toString().toLowerCase(); } async request(action, cardCode) { const deviceCode = await this.getDeviceCode(); const params = { appid: this.appid, card_code: cardCode, device_code: deviceCode, timestamp: Math.floor(Date.now() / 1000), nonce: Math.random().toString(36).substring(2) }; params.sign = this.generateSign(params); const response = await fetch(this.apiUrl + '/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }); return await response.json(); } verify(cardCode) { return this.request('verify', cardCode); } activate(cardCode) { return this.request('activate', cardCode); } heartbeat(cardCode) { return this.request('heartbeat', cardCode); } logout(cardCode) { return this.request('logout', cardCode); } }
// Android (Java) public class HjAuth { private String apiUrl, appid, appkey, deviceCode; public HjAuth(Context context, String apiUrl, String appid, String appkey) { this.apiUrl = apiUrl; this.appid = appid; this.appkey = appkey; SharedPreferences prefs = context.getSharedPreferences("hj_auth", Context.MODE_PRIVATE); this.deviceCode = prefs.getString("device_code", null); if (this.deviceCode == null) { this.deviceCode = "ANDROID_" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); prefs.edit().putString("device_code", this.deviceCode).apply(); } } private String generateSign(Map<String, String> params) throws Exception { List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); StringBuilder sb = new StringBuilder(); for (String key : keys) { String value = params.get(key); if (!key.equals("sign") && value != null && !value.isEmpty()) { sb.append(key).append("=").append(value).append("&"); } } sb.append("appkey=").append(appkey); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(appkey.getBytes(), "HmacSHA256")); return new BigInteger(1, mac.doFinal(sb.toString().getBytes())).toString(16); } }
// iOS (Swift) import CryptoKit class HjAuth { private let apiUrl: String, appid: String, appkey: String private var deviceCode: String { UserDefaults.standard.string(forKey: "device_code") ?? { let code = "IOS_" + (UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString).replacingOccurrences(of: "-", with: "") UserDefaults.standard.set(code, forKey: "device_code") return code }() } init(apiUrl: String, appid: String, appkey: String) { self.apiUrl = apiUrl; self.appid = appid; self.appkey = appkey } private func generateSign(_ params: [String: String]) -> String { let sorted = params.filter { $0.key != "sign" && !$0.value.isEmpty }.sorted { $0.key < $1.key } var str = sorted.map { "\($0.key)=\($0.value)" }.joined(separator: "&") + "&appkey=" + appkey let key = SymmetricKey(data: Data(appkey.utf8)) return HMAC<SHA256>.authenticationCode(for: Data(str.utf8), using: key).map { String(format: "%02x", $0) }.joined() } }
// Flutter - 添加依赖: device_info_plus, shared_preferences, crypto, http import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; class HjAuth { final String apiUrl, appid, appkey; String? _deviceCode; HjAuth({required this.apiUrl, required this.appid, required this.appkey}); Future<String> getDeviceCode() async { if (_deviceCode != null) return _deviceCode!; final prefs = await SharedPreferences.getInstance(); final stored = prefs.getString('device_code'); if (stored != null) { _deviceCode = stored; return stored; } final deviceInfo = DeviceInfoPlugin(); String id = Platform.isAndroid ? (await deviceInfo.androidInfo).id : (await deviceInfo.iosInfo).identifierForVendor ?? ''; _deviceCode = 'FLUTTER_' + id; await prefs.setString('device_code', _deviceCode!); return _deviceCode!; } String _generateSign(Map<String, dynamic> params) { final sorted = params.keys.toList()..sort(); final str = sorted.where((k) => k != 'sign' && params[k] != '' && params[k] != null).map((k) => '$k=${params[k]}').join('&'); return Hmac(sha256, utf8.encode(appkey)).convert(utf8.encode('$str&appkey=$appkey')).toString(); } }

六、对接要求

  • ✅ 授权模式判断(根据 auth_mode 字段决定是否显示授权框)
  • ✅ 卡密输入界面
  • ✅ 登录/激活功能(调用verify和activate接口)
  • ✅ 登出功能(调用logout接口)
  • 定时心跳检测(60-120秒)
  • ✅ 错误码处理
  • ✅ 设备码支持(开启设备码绑定时必需)

授权模式使用示例

// 调用接口后检查 auth_mode 字段 const result = await hjAuth.verify(cardCode, deviceCode); if (result.data.auth_mode === 0) { // 授权模式已关闭,跳过授权框,直接进入主程序 startMainApp(); } else { // 授权模式已开启,需要验证卡密 if (result.data.status === 1) { // 卡密有效,进入主程序 startMainApp(); } else { // 显示授权框,要求用户输入卡密 showAuthDialog(); } }