接入流程

① 后台创建应用 → 拿到 AppKey / 公钥 / AppSecret ② 上传 APK → 云端加固并自动注入 AppKey 与公钥 ③ 下载加固包 → 分发给用户 ④ 后台生成卡密 → 发放给用户激活 ⑤ 用户激活后进入应用,运行期自动心跳校验
如果你希望自行集成(不使用加固注入),可直接在自己工程中引入 SDK, 并手动在初始化时传入 AppKey 与平台公钥。

凭证说明

凭证用途存放位置
AppKey标识应用,随请求明文传输客户端(加固注入)
平台公钥RSA 公钥,用于加密 AES 密钥客户端(加固注入)
AppSecret开放 API 的 HMAC 签名密钥仅服务端
平台私钥解密请求中的 AES 密钥仅服务端
AppSecret 与平台私钥绝不可打包进客户端。 前者用于开发者服务端调用开放 API,后者由平台保管。

初始化

LingDunConfig config = new LingDunConfig.Builder() .appKey("sk_xxxxxxxxxxxxxxxx") .publicKey(PUBLIC_KEY_PEM) .apiBase("https://api.your-domain.com/api/v1") .build(); LingDun.init(getApplicationContext(), config);

卡密激活

LingDun.activate("ABCD-EFGH-IJKL-MNOP", new LingDunCallback() { @Override public void onResult(boolean ok, LicenseInfo info) { if (ok) { // 激活成功,info.expireAt 为到期时间 } } });

校验授权

LingDun.check(new LingDunCallback() { @Override public void onResult(boolean authorized, LicenseInfo info) { if (authorized) { // 授权有效,进入主流程 } else { LingDun.showActivateActivity(MainActivity.this); } } });

心跳上报

应用进入前台时调用一次 check() 即可。心跳周期由后台「应用配置」的 heartbeat_interval 下发(默认 3600 秒),长在线应用可据此定时调用 heartbeat()。心跳用于更新活跃状态、下发最新公告与更新提示。

加密方案

全部终端接口使用 RSA + AES 混合加密,单次请求流程:

  • 客户端随机生成 32 字节原始密钥 ak 与随机 16 字节 IV
  • 业务数据 JSON 用 AES-256-CBC 加密,密文格式为 base64(IV‖密文) → payload
  • ak 用平台 RSA 公钥加密(PKCS1 v1.5)→ secret(Base64)
  • 服务端用私钥还原 ak,以 SHA256(ak)[0..31] 派生真实密钥后解密 payload
  • 响应结果用同一个 ak 加密返回,客户端本地解密即可
RSA 为 2048 位,Android 端对应 RSA/ECB/PKCS1Padding; AES 为 AES/CBC/PKCS7Padding。示例代码见 sdk/android/Crypto.java。

请求结构

// 外层:明文 JSON { "app_key": "sk_xxxxxxxxxxxxxxxx", "secret": "<RSA-PKCS1 加密的 32 字节随机密钥, Base64>", "payload": "<base64(IV‖AES-256-CBC 密文)>" } // payload 解密后的明文(接口由 URL 决定,不含 action 字段) { "ts": 1793091637, // 秒级时间,偏差 300s 内有效 "nonce": "a1b2c3d4e5f6", // 随机串,窗口内不可重复 "fingerprint": "<设备指纹>", "card_no": "ABCD-EFGH-IJKL", // 仅 activate 需要 "device_info": { "model": "Pixel 8", "os_version": "14" } }

ts 与 nonce 位于 payload 明文内部, 服务端据此做时间窗校验与窗口内去重,共同防重放。

响应结构

// 通道层成功 { "code": 0, "msg": "ok", "encrypted": true, "payload": "<用同一 AES 密钥加密的结果>" } // payload 解密后 { "code": 0, "msg": "授权有效", "data": { "authorized": true, /* ... */ }, "server_time": 1793091637, "action": "check" } // 通道层失败(app_key 无效 / 超窗 / nonce 重放 / 解密失败) { "code": 1001, "msg": "请求已过期或重复提交", "encrypted": false, ... }
外层 code=0 只代表加密通道建立成功,不代表授权通过。 真正的业务结果码在 payload 解密后的内层 code。

activate · 卡密激活

// POST /api/v1/activate — payload 明文 { "ts": 1793091637, "nonce": "a1b2c3d4e5f6", "fingerprint": "<设备指纹>", "card_no": "ABCD-EFGH-IJKL", "device_info": { "model": "Pixel 8", "brand": "Google", "os_version": "14", "sdk_version": 34 } } // 解密后响应 data { "authorized": true, "license_type": "card", "license_type_text": "卡密授权", "is_permanent": false, "expire_time": 1795683637, "expire_time_str": "2026-11-26 17:40:37", "days_left": 30, "token": "<会话令牌>", "heartbeat_interval": 3600, "offline_grace_hours": 72, "notice": null, "update": { "has_update": false, "force": false } }
同一设备再次激活新卡密时为续期累加,到期时间叠加,不会覆盖首次激活的卡密。

heartbeat · 心跳上报

// POST /api/v1/heartbeat — payload 明文 { "ts": 1793091637, "nonce": "a1b2c3d4e5f6", "fingerprint": "<设备指纹>" } // 解密后响应 data(结构与 activate 一致,附带公告与更新) { "authorized": true, "expire_time": 1795683637, "days_left": 30, "server_time": 1793091637, "notice": { "id": 3, "title": "版本更新", "type": "dialog" }, "update": { "has_update": true, "force": false } }

check · 校验授权

// POST /api/v1/check — payload 明文(同 heartbeat) { "ts": 1793091637, "nonce": "...", "fingerprint": "..." } // 未授权时 { "code": 2001, "msg": "未授权", "data": { "authorized": false } }

另有明文探测接口 GET /api/v1/ping,用于排查网络连通性, 返回 {"code":0,"msg":"pong","data":{"server_time":...,"version":"1.0.0"}}。

结果码

code含义客户端建议
0成功正常放行
1001参数错误 / 时间窗超期 / nonce 重复检查参数并校准设备时间
1002签名校验失败检查 app_secret 与排序算法
1003AppKey 不存在提示联系开发者
1004应用已停用提示联系开发者
2001未授权弹出激活界面
2002授权已过期引导续费
2003设备数超出限制提示先解绑其他设备
2004卡密不存在或已失效提示卡密错误
2005卡密已被使用提示已激活
2006卡密已过期提示已过期
2007设备已被封禁提示违规并退出
2008请求无效或已重复重新生成 nonce
2009试用权限已使用提示输入卡密激活
5000服务器内部错误稍后重试