Electron License 激活系统集成开发步骤

需求背景

①通过node license.js获取license,发给用户;

②Electron打包安装后,打开软件需要先弹窗,需要用户输入我们发给他们的license,license匹配正确才能使用软件。否则提示无效或不匹配;

功能特性

  • 首次运行需输入 License 才可使用
  • License 格式包含签名与过期时间
  • 本地缓存激活信息(license.dat)
  • 启动时自动校验 License 是否有效及是否过期
  • Vue 界面右上角实时展示 License 到期时间

License Token 格式:

javascript 复制代码
Base64(JSON payload) + '.' + HMAC-SHA256 签名

示例 payload:

json 复制代码
{
  "userId": "LiuZhe19981204_&#",
  "expire": "2025-12-31"
}

一、 安装一个弹窗组件

复制代码
npm install electron-prompt

二、根目录 创建 license.js

ini 复制代码
const fs = require('fs');
const crypto = require('crypto');

const secret = 'autocontrol-jidian';
const payload = {
  userId: 'Leaf19950212_&#',
  expire: '2025-12-31' // 设置过期日期
};

const raw = JSON.stringify(payload);
const signature  = crypto.createHmac('sha256', secret).update(raw).digest('hex');
const token = Buffer.from(raw).toString('base64') + '.' + signature;

console.log("token", token)
fs.writeFileSync('license.dat', token);

三、添加license校验方法, 创建 license-check.ts

license-check.ts 要和mian.ts 在同一目录下,否则无法被构建

typescript 复制代码
// license-check.ts 要和mian.ts 在同一目录下,否则无法被构建
import * as crypto from "crypto";

const secretKey = "autocontrol-jidian";

export interface LicenseResult {
  valid: boolean;
  reason?: string;
  payload?: {
    userId: string;
    expire: string;
  };
}

export function checkLicense(token: string): LicenseResult {
  try {
    const [encodedPayload, signature] = token.split(".");
    const rawPayload = Buffer.from(encodedPayload, "base64").toString("utf-8");
    const expectedSig = crypto.createHmac("sha256", secretKey).update(rawPayload).digest("hex");

    if (expectedSig !== signature) {
      return { valid: false, reason: "签名不一致" };
    }

    const payload = JSON.parse(rawPayload);
    const now = new Date();
    const expire = new Date(payload.expire);

    if (now > expire) {
      return { valid: false, reason: `License 已于 ${payload.expire} 过期`, payload };
    }

    return { valid: true, payload };
  } catch (err) {
    return { valid: false, reason: "License 格式错误" };
  }
}

四、main.ts引入license-check.ts

引入,并在app.whenReady().then中加入以下【集成 license 检查 + 弹窗逻辑】:

typescript 复制代码
const prompt = require('electron-prompt');//引入弹窗组件
import { checkLicense, LicenseResult  } from './license-check';//引入许可证验证函数
let licensePayload: { userId: string; expire: string } | null = null;

app.whenReady().then(async() => {
// -------------------start:集成了 license 检查 + 弹窗逻辑------------------
const licensePath = path.join(app.getPath("userData"), "license.dat");

  // 判断 license 文件是否存在
  if (!fs.existsSync(licensePath)) {
    const inputToken = await prompt({
      title: "激活软件",
      label: "请输入 License:",
      inputAttrs: { type: "text" },
      type: "input",
    });

    if (!inputToken) {
      dialog.showErrorBox("未授权", "必须输入 License,程序即将退出。");
      app.quit();
      return;
    }

    // 激活成功时保存 payload 激活成功时保存 payload
    const result: LicenseResult = checkLicense(inputToken.trim());
    if (result.valid) {
      licensePayload = result.payload!;
      fs.writeFileSync(licensePath, inputToken.trim());
    } else {
      dialog.showErrorBox("激活失败", result.reason || "License 无效,请联系管理员。");
      app.quit();
      return;
    }

    // 写入本地 license 文件
    fs.writeFileSync(licensePath, inputToken.trim());
  } else {
    const savedToken = fs.readFileSync(licensePath, "utf-8").trim();
    const result = checkLicense(savedToken);
    if (result.valid) {
      licensePayload = result.payload!;
    } else {
      dialog.showErrorBox("License 无效", result.reason || "本地 License 被篡改或过期,程序将退出。");
      fs.unlinkSync(licensePath);
      app.quit();
      return;
    }
  }
// -------------------end:集成了 license 检查 + 弹窗逻辑------------------

// -------------------start:通过校验后,启动主窗口和其他逻辑------------------
// 启动主窗口和其他逻辑
  createWindow()
  // 同步数据库
  createDatabase()

  session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
    callback({
      responseHeaders: {
        ...details.responseHeaders,
        "Content-Security-Policy": ["script-src 'self' 'unsafe-eval' blob:; worker-src 'self' blob:"],
      },
    })
  })

  app.on("activate", () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
// -------------------end:集成了 license 检查 + 弹窗逻辑------------------
})

五、在preload.ts暴露获取license相关的方法

便于前端查询和显示license过期时间

arduino 复制代码
contextBridge.exposeInMainWorld("electronAPI", {
  //.....
  // license相关
  getLicenseInfo: () => ipcRenderer.invoke("get-license-info"),
}

六、前端页面显示license过期

xml 复制代码
<!-- 动态显示 License 有效期 -->
<template>
<div class="license-banner" v-if="licenseExpire">
   License 有效至:{{licenseExpire }}
</div>
</template>
  
<script setup>
import { ref, reactive, onMounted } from 'vue'
const licenseExpire = ref("");
onMounted(() => {
  // 确保在 preload 中contextBridge.exposeInMainWorld暴露了 Electron API,并且添加了getLicenseInfo方法
  window.electronAPI.getLicenseInfo().then((info) => {
    console.log("License Info:", info);
    if (info?.expire) {
      licenseExpire.value = info.expire;
    }
  });
});
</script>

<style scoped>
/* license 样式 */
.license-banner {
  position: absolute;
  top: 10px;
  right: 20px;
  font-size: 12px;
  color: #888;
}
</style>

七、测试用例

场景 预期行为
没有 license.dat 弹窗要求输入
输入正确 license 软件启动并保存激活状态
第二次启动 自动读取,无需输入
修改 license 提示签名不一致,退出程序
license 过期 提示已过期,退出程序
页面 UI 显示到期日期与剩余天数
相关推荐
冰糖雪梨dd1 小时前
h() 函数
前端·javascript·vue.js
每天都想睡觉的19001 小时前
在Vue、React 和 UniApp 中实现版本比对及更新提示
前端·vue.js·react.js
pany1 小时前
第一款 AI 友好的开源 Vue 模板 —— V3 Admin Vite / MobVue
前端·vue.js·aigc
Java陈序员1 小时前
又一款基于 SpringBoot + Vue 实现的开源新零售商城系统!
vue.js·spring boot·uni-app
孜然卷k1 小时前
C#项目 在Vue/React前端项目中 使用使用wkeWebBrowser引用并且内部使用iframe网页外链 页面部分白屏
前端·vue.js·react.js·c#
前端开发爱好者2 小时前
Vue 3.6 将正式进入「无虚拟 DOM」时代!
前端·javascript·vue.js
徐小夕2 小时前
pxcharts-pro, 支持百万数据渲染的多维表格编辑器
前端·javascript·vue.js
独立开阀者_FwtCoder3 小时前
前端开发的你,其实并没有真的掌握img标签!
前端·vue.js·github
晓得迷路了3 小时前
栗子前端技术周刊第 89 期 - TypeScript 5.9 Beta、VSCode v1.102、Angular 20.1...
前端·javascript·typescript
江城开朗的豌豆3 小时前
多个组件库混用导致JS爆炸?看我如何瘦身70%!
前端·javascript·vue.js