1、需求分析
客户提了需求,希望能够限制软件使用的时长,具体实现细节没有做要求。 因为客户要将软件卖给他的客户,希望能够按照使用时长来收费,类似很多软件的会员机制,充值1个月,1年等等。 但我们的软件使用有一个前提,不会用到外网,使用的是局域网环境。所以,不会用到公司的云数据库。
2、实现方案
出于时间的考虑,采取了登录页面限制登录的方案来控制软件的使用时间。
- 功能细节:进入登陆页面后,会判断当前设备的软件是否激活;如果没有激活,则弹出一个输入框,要求用户输入激活码,并且显示了当前设备的cpu序列号,激活成功后才能正常登录;如果已经激活,则不会显示激活码输入框,可以正常登录,并且在软件下方会显示软件的有效期。
- 激活码的生成:激活码由两部分组成,cpu序列号 + 有效时间。 cpu序列号基本上是唯一的,可以作为每一台设备的唯一标识,因此在生成激活码时,需要用户提供当前设备的cpu序列号。
3、页面效果
- 软件未激活
进入登录页面,如果软件未激活,则弹出输入框,并且给出当前电脑的cpu序列号。用户无法点击取消按钮,只有输入正确的激活码,并且点击以后弹框才会消失。
4、代码实现
- 登录页jsp代码
(async function() {
const response = await fetch('active/code/get');
const code = await fetch('active/code/cpu');
const codeString = (await code.text()).toString();
const data = (await response.text()).toString();
// 获取a标签元素
var aTag = document.querySelector('a[href="https://beian.miit.gov.cn/"]');
// 判断是否弹出激活框
if (response.ok) {
if (data === 'fail') {
aTag.innerHTML = 'HZR-2023-3.0 软件有效期: 暂未激活';
let userInput;
while (true) {
userInput = prompt("本机序列号是:" +codeString+"\n"+
"请提供序列号,联系管理员获取激活码激活", codeString);
if (userInput == null) {
continue;
}
try {
const response = await $.ajax({
url: "active/code/check",
method: "GET",
data: {"code": userInput}
});
if (response === 'success') {
alert("激活成功!");
aTag.innerHTML = 'HZR-2023-3.0';
break;
} else {
alert("激活码错误或过期,请重新输入!");
}
} catch (err) {
console.error(err);
alert("网络请求失败,请重试!");
}
}
}
else {
aTag.innerHTML = 'HZR-2023-3.0 软件有效期: '+data;
}
} else {
}
})();
- 后台java逻辑代码
查询是否激活
public String get() throws SocketException {
String cpuProcessorId = ServerAddressUtils.getCPUProcessorId();
// 如果一台设备用了多个
SoftwareActive softwareActive = activeMapper.selectByCPU(cpuProcessorId);
if (softwareActive == null) {
return "fail";
}
// 验证有效期
long limitTimeMillis = 0;
try {
limitTimeMillis = JsonUtiles.JsonUtils.jsonToPojo(softwareActive.getTime(),Long.class);
} catch (Exception e) {
return "fail";
}
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis >= limitTimeMillis) {
// 删除该条记录, 避免影响重新激活
activeMapper.deleteOneByCpu(cpuProcessorId);
return "fail";
}
// 创建SimpleDateFormat对象用于格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
// 将毫秒值转换为指定格式的字符串
String dateString = sdf.format(new Date(limitTimeMillis));
return dateString;
}
输入激活码激活
public String test(String code) throws SocketException {
if (code==null || code ==""){
return "激活码不能为空";
}
// 1. 从数据库中查询激活码是否被使用
SoftwareActive dbData = activeMapper.oneByActiveCode(code);
if (dbData != null) {
return "激活码已被使用";
}
// 2. 对激活码解码
String decrypt = null;
try {
decrypt = AESUtils.Decrypt(code, "bslszmm,zyszcsz.");
} catch (Exception e) {
return "激活码不正确";
}
if(decrypt == null || decrypt =="") {
return "激活码不正确";
}
String[] split = decrypt.split(";"); // cup ; 时间
long limitTimeMillis = JsonUtiles.JsonUtils.jsonToPojo(split[1],Long.class);
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis >= limitTimeMillis) {
return "激活码已过期";
}
// 3.激活成功,将激活时间添加到数据库
// String mac = getMac();
// cup 和 code 编码, 到期时间
activeMapper.insertOne(code,split[0],split[1]);
return "success";
}
生成激活码工具方法
public static void main(String[] args) throws Exception {
// 获取序列号
String cpuProcessorId = "BFEBFBFF00906C1";
// 获取当前时间的毫秒值
long currentTimeMillis = System.currentTimeMillis();
// 创建Calendar对象并设置时间为当前时间
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(currentTimeMillis);
// 将时间加上3个月
calendar.add(Calendar.MONTH, 3);// 3个月
// 获取指定时间后的毫秒值
long threeDaysLaterMillis = calendar.getTimeInMillis();
// 拼接
String code = cpuProcessorId + ";" + threeDaysLaterMillis;
// 加密
String decrypt = AESUtils.Encrypt(code, "bslszmm,zyszcsz.");
System.out.println(decrypt);
}
获取本机cpu序列号
public static String getCPUProcessorId() {
long start = System.currentTimeMillis();
String serial = null;
try {
Process process = Runtime.getRuntime().exec(
new String[] { "wmic", "cpu", "get", "ProcessorId" });
process.getOutputStream().close();
Scanner sc = new Scanner(process.getInputStream());
String property = sc.next();
serial = sc.next();
System.out.println(property + ": " + serial);
System.out.println("time:" + (System.currentTimeMillis() - start));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return serial;
}
5、后续改进
目前存在的问题是,激活码需要我们给客户生成。客户希望能够自己生成激活码,自己指定激活码的有效期,然后再给到他们的客户。
大概有两种思路,一种是将生成代码的工具放到我们的云网站上,客户登录云网站生成。二是,给用户写另外一个生成激活码的工具,单独生成。 目前考虑使用第二种方案。