License Manager v1.2.1 把“试用许可”纳入产品配置:创建产品时可以开启试用并设置有效时长,客户端软件则可以自动获取试用许可证。这个变化看似只是多了一个开关,实际影响了获客流程、客户端激活逻辑以及服务端的防滥用策略。
试用从人工发放变成产品能力
过去如果试用依赖人工创建许可证,用户通常需要提交机器码、等待审核,再把许可证导入软件。流程越长,试用转化越容易中断。
v1.2.1 提供的核心能力可以归纳为两点:
- 创建产品时启用或关闭试用。
- 为产品设置统一的试用时长,并允许软件自动领取试用许可证。
这意味着客户端可以在首次启动时检测本地授权状态:已有正式许可证就正常验证;没有许可证则尝试申请试用;试用过期后再引导用户购买或导入正式许可证。
试用时长应该由授权平台决定,而不是由客户端自行计算。客户端本地时间容易被修改,也可能因时区、休眠或系统重装产生偏差。更稳妥的做法是让许可证携带服务端签发的开始时间、到期时间和签名,客户端只负责验证与展示。
客户端自动领取的推荐流程
一个可控的自动试用流程通常包含以下步骤:
- 客户端生成或读取稳定的设备标识。
- 检查本地是否已有可用许可证。
- 没有许可证时,向授权服务申请当前产品的试用许可。
- 服务端判断该产品是否开放试用,以及当前设备或用户是否已经领取过。
- 客户端保存返回的许可证,并使用授权 SDK 或签名验证逻辑进行校验。
- 网络不可用时,根据产品策略进入有限离线模式,而不是重新生成试用期。
下面是一个可改造的 Python 示例。由于摘要没有公布 License Manager 的实际接口路径、字段和认证方式,示例假设平台提供 POST /api/v1/trials;接入时需要替换地址、请求字段,并按平台文档加入认证信息。
#!/usr/bin/env python3
import hashlib
import json
import os
import platform
import urllib.error
import urllib.request
from pathlib import Path
API_BASE = os.environ.get("LICENSE_API_BASE", "http://127.0.0.1:8080")
PRODUCT_ID = os.environ.get("LICENSE_PRODUCT_ID", "desktop-pro")
LICENSE_FILE = Path.home() / ".example-app" / "license.json"
def device_fingerprint() -> str:
source = "|".join([
platform.node(),
platform.system(),
platform.machine(),
])
return hashlib.sha256(source.encode("utf-8")).hexdigest()
def request_trial() -> dict:
payload = json.dumps({
"product_id": PRODUCT_ID,
"device_id": device_fingerprint(),
"client_version": "1.0.0",
}).encode("utf-8")
request = urllib.request.Request(
f"{API_BASE}/api/v1/trials",
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=10) as response:
return json.load(response)
def main() -> None:
if LICENSE_FILE.exists():
print(f"许可证已存在:{LICENSE_FILE}")
return
try:
license_data = request_trial()
except urllib.error.HTTPError as exc:
message = exc.read().decode("utf-8", errors="replace")
raise SystemExit(f"试用许可证申请失败:HTTP {exc.code} {message}")
except urllib.error.URLError as exc:
raise SystemExit(f"无法连接授权服务:{exc.reason}")
required = {"license", "expires_at"}
if not required.issubset(license_data):
raise SystemExit("授权服务返回的数据不完整")
LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
LICENSE_FILE.write_text(
json.dumps(license_data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"试用许可证已保存,有效期至 {license_data['expires_at']}")
if __name__ == "__main__":
main()
运行前设置实际授权服务地址和产品标识:
export LICENSE_API_BASE="https://license.example.com"
export LICENSE_PRODUCT_ID="your-product-id"
python3 acquire_trial.py
这段代码只演示“发现无许可证后申请并保存”的流程。生产环境还必须验证许可证签名,不能因为 JSON 中存在 expires_at 就信任它;设备指纹也应使用平台允许且符合隐私政策的稳定标识。
服务端不能只检查客户端参数
自动领取降低了试用门槛,也扩大了重复领取的风险。服务端至少需要控制以下边界:
- 试用开关和试用时长只能从产品配置读取,不能接受客户端提交的天数。
- 对产品、设备、账号或组织设置合理的领取次数限制。
- 对重复请求返回同一张有效试用许可证,避免网络重试产生多张许可。
- 记录签发时间、到期时间、产品标识、设备标识和许可证状态。
- 对接口设置速率限制,并监控短时间内的大量设备注册。
- 明确卸载重装、更换硬盘、虚拟机快照和系统时间回拨时的处理规则。
如果客户端需要离线运行,可以给许可证签名并允许本地验签,同时设置有限的离线宽限期。完全依赖在线验证会受网络故障影响,完全依赖本地时间又容易被绕过,两者之间需要根据软件价值和使用场景取舍。
上线前的检查清单
启用试用前,建议先在测试产品上验证完整生命周期:首次领取、重复领取、离线启动、许可证到期、正式许可覆盖试用许可,以及服务端不可用时的提示。
还要确认客户端不会静默覆盖正式许可证,不会在申请失败时无限重试,也不会把设备原始信息写入日志。对于已经发布的旧客户端,应检查它们能否识别新的试用许可证类型;如果不能,就需要通过版本门槛或兼容字段逐步上线。
v1.2.1 提供了自动试用所需的产品配置入口,但真正稳定的落地仍取决于客户端状态机、服务端幂等控制、许可证签名验证和异常场景测试。把这些环节补齐后,试用许可才能既缩短用户开始体验的路径,又不削弱授权体系的边界。