第六章 Electron|Node 实现license激活机制

一、license是什么 ✨ ⭐️   

license许可证,一般用于软件的授权,我个人的理解就和我们平时的登录差不多。只是说登录时需要我们输入用户名和密码,license一般是开发方提供给你一串加密后的文本,通过这个文本进行一个系统的授权,并且只需要授权一次就可以。这个license一般是会携带用户的mac地址、使用期限、激活日期等等啊。这个可以根据自己的需求去设置。最后把这些信息进行一个非对称加密。用户拿到加密后的license以后可以请求服务器,服务器拿到license后解密进行对比。从而实现激活软件。这只是我目前掌握的东西,里面可能还有很多复杂的逻辑,也有可能不是这样子的逻辑。下面就我的理解制作一个属于我们自己软件的license。

二、安装依赖  

这里的话我选择使用JSEncrypt插件。它是非对称加密方式,也就是通过公钥加密,私钥解密。通俗一点就是,开门要一个钥匙,关门要另一把钥匙。对称加密就是只有一把钥匙就可以开门关门。

yarn add jsencrypt -D

我当前安装的版本

三、生成license   

在使用插件之前,我们得具备一对RSA密钥对,这个网上有很多工具可以生成。在线RSA密钥对生成工具 - UU在线工具


生成了license,比如此时需要进行验证。

四、制作激活页面   

先安装相关的依赖

yarn add getmac pinia
yarn add @vueuse/electron -D

4.1、创建pinia状态管理器,目录结构如下 

第六章 Electron|Node 实现license激活机制_第1张图片

4.1.1、src/pinia/modules/user.modules.ts

import { defineStore } from 'pinia';
import {useIpcRenderer} from "@vueuse/electron";
import { useDateFormat } from '@vueuse/core'
import JSEncrypt from 'jsencrypt';
/**
 * @Description: 用户
 * @CreationDate 2023-05-15 17:07:16
 */
interface UserState {
    is_activation: boolean // 是否激活
    privateKey: string
}

/**
 * @Description: 用户状态管理器
 * @Author: Etc.End([email protected])
 * @Copyright: TigerSong
 * @CreationDate 2023-05-15 17:07:49
 */
export const userModule = defineStore({
    id: 'user',
    state(): UserState {
        return {
            is_activation: false,
            privateKey: `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC46jpLQ1eAGHL7
gRDGF5AJ9Q//tLjW770md3fcwtI2kGladZ14HVmbH/BqNOI5xmlpJniB4oedAL8i
QayTehvEzNhFqWzxBC41UX8xyc5DkYB1rJBzwPvh7/GZlzZ/a69Sy89Pqy5wvuaO
FKIMK5KSEeOBUjt0F+qNPtKVLsvBgMTB59xxfBZF+YifgfHXUMY9Cy2IXu52kV8w
CQxtGmSilyxL0R9qcZyF31jz+sHgWBVtJz7Ob6jeLHH3I+3yyi+yH/HhgZw7xlDn
pFMIPOKhrUAux7QodWNgU/pnZmmT3gROAsLliiwvrU7D2rs+WYxpd7IQr4SiV6PL
aq4parBXAgMBAAECggEALYRafRRCgaGDDC2k913tcsYD/il6Jk40/TcDJjA+lnfN
txqkfGCdIfYms734wcf5QozZtP8R6q+4XLJVzKeOFk9mHR+rVVh2F2HMMXE/eJpk
SJMFq7ihR+hMTEZQf+T97x+EFFRKxi33ipnBmcVP+uy0V6zqPZV1gvcn1tkCBstD
s7F9EvmoLjWoFMroU0dO7D0ncJVcGQafXKxaf5r3W22E9lwhCLKhXahNRWkHs96U
LaLyZam46xHPAaSXqQ1eOmyoZY2bIX9cKKB4PKAdOBi2VbTNZoJTeKviQ4Xs8ESu
pOXfix36tZ5u9W2cQkE243fZt6Q7DTsiQ9AZSR4+YQKBgQD2Rj74U4liGtT8xszJ
0sAoYeJPHFyFYQiafIoT7W565WvsswwVmF5o7pn4ZAjnoHFdyQS3D2tK5OeduDdf
4PHx3ywq1PwLTrgUj10CyL15BrEn90VEJSEWSiHCSZjm37rGWVhzlge0hrzkMwO6
09EdZqcekWBNq3XD9FvLzeV90wKBgQDAN6he//GjWXOJtoKMRpYKaGSrK0A5waOM
mWlm4nsdwGRvNLazzAzSNrJyTvjROMF0u9NPr9glh9tcqkTUcFg4n0zagEVRNcKL
sMEsntVkxyI0jWgb6vVvojwzrrdZmYPvB5KwimQ5ROHe+8nV019YqBBJnnIFPT4t
TELq2BF87QKBgQDNIV26Afrg2HCny/8v7HdaK44RTxJRlq1P4IQybQYlH4txsQFT
y4J37KYbG1e/dwh2kcV3pUQ9McUqvhKBriBY0wc69gSqdnslxPQ4KXSIpmZRX8k2
JacVpdHQvvS4+YndRPZD8KeiWshjW4qzx1LbJnH1KCoLB9Ij0hnT/EA3OQKBgDjn
ASAGcrkhvPNSpTjzmG1CVDLb3ep7KXhw3eQIPdwj3VeSale1m0IL0S3HtR7yx0pQ
ZBDeBIWvvz+iZDfjfipc9jpk6KBO4uXJkJYt+wwXa0fVaLGDD99ZTqsaGMsciBMV
0dYTUfImMxt4vFphdYNgVVoF3skwRRzRy6mMBzlNAoGBALDvoMgCZwI0peClxste
u3XpTvuzr/9tncXtvXhcvXuTjMu5DOteYY+A0kewI2JrG2ZWFQ74ahygB20HgszR
erRGogi+IVpOsKQ+mDi6KMDrqZ2BYO5ALpfmYQZC/f6KIx/CocxsHwKsKkLvEacj
GYTwL1iYHvt2/YTiHIehhtFp
-----END PRIVATE KEY-----`,
        }
    },
    actions: {
        /**
         * @Description: 校验激活码是否正确
         * @CreationDate 2023-06-07 15:02:42
         */
        sendActivate(str: string) {
            const that = this
            return new Promise((resolve, reject) => {
                // 首先去查找激活文件是否存在 如果存在的话就使用激活文件中的激活码进行校验
                try {
                    const fs = require('fs')
                    let path = `${process.cwd()}/config/core.tiger`
                    if (import.meta.env.MODE !== 'development') {
                        const currentFilePath = new URL(import.meta.url).pathname.slice(1);
                        const currentPath = currentFilePath.split('resources/app.asar/dist')[0]
                        path = `${currentPath}config/core.tiger`
                    }
                    let res = fs.readFileSync(path, 'utf-8');
                    if (res) {
                        const decode: string = atob(res)
                        that.is_activation = false
                        resolve(that.verifyRegistration(decode, 'file'))
                    } else {
                        reject(false);
                    }
                } catch (e) {
                    // 首先去查找激活文件是否存在 如果不存在的话就使用页面传入的激活码进行校验
                    if (str) {
                        that.is_activation = false
                        return resolve(that.verifyRegistration(str, 'input'))
                    } else {
                        resolve(false)
                    }
                }
            })
        },
        /**
         * @Description: 校验激活码是否匹配
         * @CreationDate 2023-06-07 15:05:22
         */
        verifyRegistration(str: string, type: string) {
            // 这里是获取用户的mac地址
            const getMac = require('getmac')
            const mac = getMac.default()
            if (mac) {
                // 进行解码
                const encryptor = new JSEncrypt()
                encryptor.setPrivateKey(this.privateKey)
                const data = encryptor.decrypt(str)
                if (data && mac.toUpperCase()) {
                    try {
                        const tsData = JSON.parse(data)
                        const YMD = useDateFormat(new Date(), 'YYYY-MM-DD')
                        const currentDate = new Date(YMD.value)
                        const endDate = new Date(tsData.date)
                        const effectiveDate = new Date(tsData.effectiveDate)
                        if (mac.toUpperCase() === tsData.mac && tsData.mark === 'Etc@_@End' && currentDate < endDate) {
                            if (type !== 'file') {
                                if (currentDate <= effectiveDate) {
                                    this.is_activation = false
                                    return false
                                }
                            }
                            this.is_activation = true
                            return true
                        }
                    } catch (e) {
                        return false
                    }
                }
                return false
            }
            return false
        },
        /**
         * @Description: 激活成功后向主程序发送命令 生成激活文件
         * @CreationDate 2023-05-17 10:48:30
         */
        activationSuccessful(str: string) {
            const baseStr = encodeURI(str)
            const ipcRenderer = useIpcRenderer()
            ipcRenderer.send('TSActivateApplication', btoa(baseStr))
        }
    },
    getters: {},
});

4.1.2、src/pinia/index.ts

import { userModule } from './modules/user.modules';

export interface IAppStore {
    userModule: ReturnType;
}

const appStore: IAppStore = {} as IAppStore;

export const registerStore = () => {
    appStore.userModule = userModule();
};

export default appStore;

4.2、创建router 【src/router/index.ts】

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

export const asyncRoutes: RouteRecordRaw[] = [
    {
        path: '/',
        component: () => import('@/views/home/index.vue')
    },
    {
        path: '/401',
        component: () => import('@/views/401.vue'),
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes: asyncRoutes,
    scrollBehavior: () => ({ left: 0, top: 0 })
})

export default router

4.3、增加src/permission.ts

import router from '@/router'
import appStore from "@/pinia";

const whiteList: string[] = ['/401']
router.beforeEach(async (to, form, next) => {
    if (appStore.userModule.is_activation) {
        if (to.path === '/401') {
            next('/')
        } else {
            next()
        }
    } else {
        if (whiteList.indexOf(to.path) !== -1) {
            next();
        } else {
            next('/401')
        }
    }
})

4.4、修改src/main.ts

import { createApp } from 'vue'

import App from './App.vue'
import router from '@/router';
import '@/permission'
import { createPinia } from 'pinia';
import { registerStore } from '@/pinia';
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App);
app.use(ElementPlus)
app.use(router)
app.use(createPinia())
registerStore()
app.mount('#app')

4.5、新增src/views/401.vue页面






4.6、新增src/views/home/index.vue页面


4.7、修改主程序electron/main.ts

const fs = require('fs')
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const remote = require("@electron/remote/main");
remote.initialize();

const NODE_ENV = process.env.NODE_ENV
let win

/**
 * @Description: electron程序入口
 * @Author: Etc.End
 * @Copyright: TigerSong
 * @CreationDate 2023-05-20 14:39:26
 */
const createWindow = () => {
    win = new BrowserWindow({
        icon: './public/logo.png',
        frame: false, // 去掉导航最大化最小化以及关闭按钮
        width: 1200,
        height: 800,
        minWidth: 1200,
        minHeight: 800,
        center: true,
        skipTaskbar: false,
        transparent: false,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false,
            webSecurity: false,
        }
    })

    try {
        const activation = fs.readFileSync('./config/core.tiger', 'utf8')
        if (activation) {
            // 这里就可以把pinia中的逻辑放在这里,如果激活码不正确的话,就不加载某些脚本。
            // 也可以根据判断激活码来生成路由或删除路由数据,方案很多,自由发挥。
        }
    } catch (e) {
        console.log('-----------------注册码读取失败', e.message)
    }

    win.loadURL(
        NODE_ENV === 'development' ? 'http://localhost:5173/' : `file://${path.join(__dirname, '../dist/index.html')}`
    )

    if (NODE_ENV === 'development') {
        win.webContents.openDevTools()
    }

    remote.enable(win.webContents);
}

let CONFIG_PATH = path.join(app.getAppPath(), '/config');
if (NODE_ENV !== 'development') {
    CONFIG_PATH = path.join(path.dirname(app.getPath('exe')), '/config');
}

app.whenReady().then(() => {
    createWindow()
    const isExistDir = fs.existsSync(CONFIG_PATH)
    if (!isExistDir) {
        fs.mkdirSync(CONFIG_PATH)
    }
})

ipcMain.on('TSActivateApplication', (evt, args) => {
    fs.writeFile(`${CONFIG_PATH}/core.tiger`, args, function(err) {
        if(err) {
            return console.log('-----------------创建激活码文件失败!')
        }
        setTimeout(() => {
            // 重启
            if (NODE_ENV !== 'development') {
                app.relaunch()
                app.exit()
            }
        }, 2 * 1000);
    })
})

/**
 * @Description: 限制只能打开一个页面
 * @CreationDate 2023-05-20 14:35:52
 */
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
    app.quit()
} else {
    app.on('second-instance', (event, commandLine, workingDirectory) => {
        if (win) {
            if (win.isMinimized()) win.restore()
            win.focus()
        }
    })
}

app.on('window-all-closed', function () {
    if(process.platform !== 'darwin') app.quit()
})

4.7、修改src/App.vue并且重启项目







五、效果浏览

1、无激活

第六章 Electron|Node 实现license激活机制_第2张图片

第六章 Electron|Node 实现license激活机制_第3张图片

2、激活

第六章 Electron|Node 实现license激活机制_第4张图片

第六章 Electron|Node 实现license激活机制_第5张图片

第六章 Electron|Node 实现license激活机制_第6张图片

3、激活后重启项目

第六章 Electron|Node 实现license激活机制_第7张图片

我是Etc.End。如果文章对你有所帮助,能否帮我点个免费的赞和收藏。

   

你可能感兴趣的:(Electron,electron,javascript,ecmascript)