Electron+Uniapp激活码验证方案

Electron+Uniapp激活码验证方案(含持久化存储)

一、整体实现逻辑

核心思路:通过Electron控制应用启动流程,首次启动时弹窗收集激活码,验证通过后存储到本地文件(持久化,关机重启不丢失);后续启动直接读取本地存储的激活码,跳过弹窗。同时,Electron将激活码作为参数传递给Uniapp打包的H5页面,供Uniapp调用。

技术依赖:Electron内置的dialog(弹窗)、fs(本地文件操作)、path(路径处理),以及Uniapp的URL参数解析能力。

二、Electron端实现(核心步骤)

2.1 项目结构说明

假设Electron项目结构如下(Uniapp打包的H5放在dist目录下):


electron-project/
├─ main.js          // Electron主进程(核心逻辑所在)
├─ preload.js       // 主进程与渲染进程通信桥接(可选)
├─ dist/            // Uniapp打包的H5文件
│  ├─ index.html
│  └─ ...(其他H5资源)
├─ config/          // 存储激活码的目录(自动生成)
│  └─ activation.json  // 激活码存储文件
└─ package.json

2.2 主进程代码(main.js)

实现激活码弹窗、本地存储、参数传递逻辑,主进程控制应用启动流程:


const { app, BrowserWindow, dialog } = require('electron');
const fs = require('fs');
const path = require('path');

// 激活码存储路径(用户目录下,避免权限问题)
const activationPath = path.join(app.getPath('userData'), 'activation.json');
let mainWindow = null;

// 读取本地激活码
function getLocalActivationCode() {
  try {
    if (fs.existsSync(activationPath)) {
      const data = fs.readFileSync(activationPath, 'utf8');
      const { code, isVerified } = JSON.parse(data);
      // 验证激活码是否有效(可替换为你的后端验证逻辑)
      return isVerified ? code : null;
    }
  } catch (err) {
    console.error('读取激活码失败:', err);
  }
  return null;
}

// 保存激活码到本地
function saveActivationCode(code) {
  try {
    // 写入激活码及验证状态(这里默认前端输入后即为有效,可加后端校验)
    fs.writeFileSync(activationPath, JSON.stringify({
      code,
      isVerified: true,
      createTime: new Date().toISOString()
    }), 'utf8');
    return true;
  } catch (err) {
    console.error('保存激活码失败:', err);
    return false;
  }
}

// 弹窗获取激活码
async function showActivationDialog() {
  const { response, inputValue } = await dialog.showInputBox({
    title: '激活应用',
    message: '请输入激活码以继续使用',
    placeholderText: '请输入激活码',
    buttons: ['确认', '取消'],
    defaultId: 0,
    // 可选:限制输入长度
    // inputAttributes: { maxlength: 32 }
  });

  // 点击确认且输入不为空
  if (response === 0 && inputValue?.trim()) {
    const code = inputValue.trim();
    // 可选:调用后端接口验证激活码有效性
    // const isLegal = await fetch('你的验证接口', { method: 'POST', body: JSON.stringify({ code }) }).then(res => res.json());
    const isLegal = true; // 临时模拟验证通过
    if (isLegal) {
      saveActivationCode(code);
      return code;
    } else {
      dialog.showErrorBox('激活失败', '输入的激活码无效,请重新输入');
      return showActivationDialog(); // 递归弹窗,直到输入有效码
    }
  } else {
    // 点击取消或输入为空,退出应用
    app.quit();
    return null;
  }
}

// 创建主窗口
async function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 桥接脚本
      nodeIntegration: false, // 禁用node集成,提高安全性
      contextIsolation: true, // 开启上下文隔离
    }
  });

  // 获取激活码(首次弹窗,后续读取本地)
  const activationCode = getLocalActivationCode() || await showActivationDialog();

  // 加载Uniapp H5页面,并携带激活码参数
  const h5Url = path.join(__dirname, 'dist', 'index.html');
  const urlWithParams = `${h5Url}?activationCode=${encodeURIComponent(activationCode)}`;
  mainWindow.loadURL(urlWithParams);

  // 窗口关闭事件
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

// 应用就绪后创建窗口
app.whenReady().then(createWindow);

// 关闭所有窗口后退出(macOS除外)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// macOS激活应用时重新创建窗口
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

2.3 预加载脚本(preload.js,可选)

若Uniapp需要通过Electron API二次获取激活码(而非URL参数),可通过预加载脚本暴露接口:


const { contextBridge } = require('electron');
const fs = require('fs');
const path = require('path');
const activationPath = path.join(require('electron').app.getPath('userData'), 'activation.json');

// 向Uniapp暴露获取激活码的方法
contextBridge.exposeInMainWorld('electronApi', {
  getActivationCode: () => {
    try {
      if (fs.existsSync(activationPath)) {
        const data = fs.readFileSync(activationPath, 'utf8');
        return JSON.parse(data).code;
      }
    } catch (err) {
      console.error('获取激活码失败:', err);
    }
    return null;
  }
});

2.4 打包配置(package.json)

确保打包时包含Uniapp的H5资源,需配置build字段(需安装electron-builder):


{
  "name": "your-app-name",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "build": "electron-builder"
  },
  "build": {
    "appId": "com.your.app",
    "productName": "你的应用名称",
    "directories": {
      "output": "release" // 打包输出目录
    },
    "files": [
      "main.js",
      "preload.js",
      "dist/**/*" // 包含Uniapp H5资源
    ],
    "win": {
      "target": "nsis", // 打包为exe安装包
      "icon": "icon.ico" // 应用图标
    }
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.9.1"
  }
}

三、Uniapp端实现(获取激活码)

Uniapp可通过两种方式获取激活码,根据需求选择:

3.1 方式一:解析URL参数(推荐,简单直接)

Electron加载H5时已将激活码拼接到URL后,Uniapp在页面初始化时解析参数即可:


// 在Uniapp的App.vue或首页onLoad生命周期中获取
export default {
  onLaunch() {
    // 获取URL参数
    const urlParams = this.getUrlParams(window.location.href);
    const activationCode = urlParams.activationCode;
    if (activationCode) {
      // 存储激活码(可存入vuex、uni.setStorage等)
      uni.setStorageSync('activationCode', activationCode);
      console.log('获取到激活码:', activationCode);
      // 后续业务逻辑(如接口请求携带激活码)
    } else {
      console.error('未获取到激活码,应用可能未激活');
      // 可选:提示用户并退出(Electron已做校验,此为兜底)
    }
  },
  methods: {
    // 解析URL参数的工具方法
    getUrlParams(url) {
      const params = {};
      const index = url.indexOf('?');
      if (index !== -1) {
        const query = url.slice(index + 1);
        query.split('&').forEach(item => {
          const [key, value] = item.split('=');
          if (key && value) {
            params[key] = decodeURIComponent(value);
          }
        });
      }
      return params;
    }
  }
}

3.2 方式二:通过Electron预加载接口获取

若URL参数方式不满足需求,可通过预加载脚本暴露的electronApi获取:


// 在Uniapp的App.vue中
export default {
  onLaunch() {
    // 确保electronApi已暴露
    if (window.electronApi) {
      const activationCode = window.electronApi.getActivationCode();
      if (activationCode) {
        uni.setStorageSync('activationCode', activationCode);
        console.log('通过Electron API获取激活码:', activationCode);
      } else {
        console.error('未获取到激活码');
      }
    } else {
      console.error('Electron API未加载');
    }
  }
}

四、关键注意事项

4.1 激活码验证安全(可选)

当前方案默认输入激活码即为有效,实际项目中建议增加后端验证:

  1. Electron弹窗获取激活码后,调用后端接口校验合法性;

  2. 验证通过后,可存储激活码+过期时间(若需时效控制);

  3. 后续启动时,除读取本地激活码,还需校验是否过期(可结合后端接口)。

4.2 本地文件权限问题

激活码存储路径使用app.getPath('userData'),该路径为用户专属目录(如Windows:C:\Users\用户名\AppData\Roaming\应用名),无需管理员权限,避免写入失败。

4.3 Uniapp打包配置

Uniapp打包H5时,需设置基础路径为相对路径(在manifest.json中设置:"h5": {"router": {"base": "./"}}),确保Electron能正常加载本地H5文件。

4.4 应用更新后激活码保留

由于激活码存储在用户目录下,而非应用安装目录,应用更新(重新打包安装)后,激活码仍会保留,无需重新输入。

五、测试流程

  1. Uniapp打包H5,将dist目录复制到Electron项目根目录;

  2. 执行npm start启动Electron应用,首次启动弹窗要求输入激活码;

  3. 输入激活码后,确认激活成功,应用加载H5并获取参数;

  4. 关闭应用,重启电脑,再次启动应用,无弹窗,直接加载H5并携带激活码;

  5. 执行npm run build打包为exe,安装后测试上述流程,验证功能正常。

给TA支持
共{{data.count}}人
人已支持
知道vue工作日志前端

vue 启动报错

2024-11-21 23:29:16

php工作日志thinkphp

Thinkphp5 模型里别名alias不生效bug【已解决】

2018-8-11 16:40:33

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索