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 显示到期日期与剩余天数
相关推荐
花菜会噎住12 小时前
Vue3核心语法进阶(computed与监听)
前端·javascript·vue.js
I'mxx12 小时前
【vue(2)插槽】
javascript·vue.js
花菜会噎住12 小时前
Vue3核心语法基础
前端·javascript·vue.js·前端框架
练习前端两年半14 小时前
Vue3 源码深度剖析:有状态组件的渲染机制与生命周期实现
前端·vue.js
533_17 小时前
[vue3 echarts] echarts 动态数据更新 setInterval
vue.js·echarts
Jimmy17 小时前
TypeScript 泛型:2025 年终极指南
前端·javascript·typescript
jstart千语17 小时前
【vue】创建响应式数据ref和reactive的区别
前端·javascript·vue.js
盗德18 小时前
Vue渲染引擎的范式革命:从虚拟DOM到Vapor模式
前端·vue.js
Dolphin_海豚19 小时前
vapor 的 IR 是如何被 generate 到 render 函数的
前端·vue.js·vapor
初遇你时动了情20 小时前
JS中defineProperty/Proxy 数据劫持 vue3/vue2双向绑定实现原理,react 实现原理
javascript·vue.js·react.js