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

news/2024/11/27 6:30:06/

一、license是什么 ✨ ⭐️ 🌟  

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

二、安装依赖  💎 💎 💎

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

yarn add jsencrypt -D

我当前安装的版本

三、生成license 🔥 🔥 🔥

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

<script lang="ts">
import {defineComponent, onMounted} from "vue";
import JSEncrypt from 'jsencrypt';export default defineComponent({setup() {const getJSEncrypt = (str: string): string => {const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOo6S0NXgBhy+4EQxheQ
CfUP/7S41u+9Jnd33MLSNpBpWnWdeB1Zmx/wajTiOcZpaSZ4geKHnQC/IkGsk3ob
xMzYRals8QQuNVF/McnOQ5GAdayQc8D74e/xmZc2f2uvUsvPT6sucL7mjhSiDCuS
khHjgVI7dBfqjT7SlS7LwYDEwefccXwWRfmIn4Hx11DGPQstiF7udpFfMAkMbRpk
opcsS9EfanGchd9Y8/rB4FgVbSc+zm+o3ixx9yPt8sovsh/x4YGcO8ZQ56RTCDzi
oa1ALse0KHVjYFP6Z2Zpk94ETgLC5YosL61Ow9q7PlmMaXeyEK+Eolejy2quKWqw
VwIDAQAB
-----END PUBLIC KEY-----`const encryptor = new JSEncrypt()encryptor.setPublicKey(publicKey) // 设置公钥return encryptor.encrypt(str) as string}const generateLicense = () => {const res = {mac: '88:ER:0A:25:YY:OP', // 这里是我随便遍的mac地址 可以通过ipconfig /all进行查询 如果不更换成本机的mac地址的话,永远的激活不成功的date: '2023-07-05', // 这个日期是产品到什么时候过期effectiveDate: '2023-06-07', // 这个日期是产品什么时候之前激活有效mark: 'Etc@_@End', // 这个的话就是随便标记的key 用于对比}const data: string = getJSEncrypt(JSON.stringify(res))console.log('👉👉👉-----------------',data)}onMounted(() => {generateLicense()})}
})
</script>

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

四、制作激活页面 🍄 🍄 🍄

先安装相关的依赖

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

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

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(710962805@qq.com)* @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 = thisreturn 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 = falseresolve(that.verifyRegistration(decode))} else {reject(false);}} catch (e) {// 首先去查找激活文件是否存在 如果不存在的话就使用页面传入的激活码进行校验if (str) {that.is_activation = falsereturn resolve(that.verifyRegistration(str))} else {resolve(false)}}})},/*** @Description: 校验激活码是否匹配* @CreationDate 2023-06-07 15:05:22*/verifyRegistration(str: 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 && currentDate <= effectiveDate) {this.is_activation = truereturn 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<typeof userModule>;
}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) {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页面

<template><div><div style="width: 100%;height: 100%;"><Header /><div style="width: 100%;height: calc(100vh - 86px);display: flex;flex-direction: column;justify-content: center;align-items: center;background: #3b3b3b;color: white!important;"><p style="width: 100%;display: flex;align-items: center;justify-content: center;">抱歉,您沒有使用权限!<el-button type="primary" link @click="openDialog">去激活</el-button></p><pre v-html="text" style="color:#009169;"></pre><p style="width: 100%;display: flex;align-items: center;justify-content: center;">Sorry, you do not have permission to use it!<el-button type="primary" link @click="openDialog">Go Activation</el-button></p></div><el-footer class="ts-layout--footer" style="padding: 0!important;"><span>@2023 Author (TigerSong 710962805@qq.com)</span></el-footer></div><el-dialog v-model="activationVisible"><el-inputv-model="activationCode":rows="10"type="textarea"placeholder="请输入激活码"@focus="activationCodeFocus"/><div v-if="message.length > 1" style="text-align: center;margin-top: 10px;" :style="{color: pass ? '#67c23a' : '#f56c6c'}">{{message}}</div><div v-if="message.length === 0" style="width: 100%;display: flex;align-items: center;justify-content: center;margin-top: 10px;"><el-button type="primary" @click="activation" style="width: 120px;" v-if="activationCode">激活</el-button></div></el-dialog></div>
</template><script lang="ts">
import {defineComponent, reactive, ref, toRefs} from "vue";
import appStore from "@/pinia";
import router from "@/router";export default defineComponent({setup() {const text = ref<string>('@@@@@@@   @@@  @@@   @@@@@@   @@@@@@@@  @@@  @@@  @@@  @@@  @@@ \n' +'@@@@@@@@  @@@  @@@  @@@@@@@@  @@@@@@@@  @@@@ @@@  @@@  @@@  @@@ \n' +'@@!  @@@  @@!  @@@  @@!  @@@  @@!       @@!@!@@@  @@!  @@!  !@@ \n' +'!@!  @!@  !@!  @!@  !@!  @!@  !@!       !@!!@!@!  !@!  !@!  @!! \n' +'@!@@!@!   @!@!@!@!  @!@  !@!  @!!!:!    @!@ !!@!  !!@   !@@!@!  \n' +'!!@!!!    !!!@!!!!  !@!  !!!  !!!!!:    !@!  !!!  !!!    @!!!   \n' +'!!:       !!:  !!!  !!:  !!!  !!:       !!:  !!!  !!:   !: :!!  \n' +':!:       :!:  !:!  :!:  !:!  :!:       :!:  !:!  :!:  :!:  !:! \n' +' ::       ::   :::  ::::: ::   :: ::::   ::   ::   ::   ::  ::: \n' +' :         :   : :   : :  :   : :: ::   ::    :   :     :   ::  \n' +'                                                                \n' +'                            .-==========                        \n' +'                         .-\' O    =====                         \n' +'                        /___       ===                          \n' +'                           \\_      |                            \n' +'_____________________________)    (_____________________________\n' +'\\___________               .\'      `,              ____________/\n' +'  \\__________`.     |||<   `.      .\'   >|||     .\'__________/  \n' +'     \\_________`._  |||  <   `-..-\'   >  |||  _.\'_________/     \n' +'        \\_________`-..|_  _ <      > _  _|..-\'_________/        \n' +'           \\_________   |_|  //  \\\\  |_|   _________/           \n' +'                      .-\\   //    \\\\   /-.                      \n' +'      ,  .         _.\'.- `._        _.\' -.`._         .  ,      \n' +'    <<<<>>>>     .\' .\'  /  \'``----\'\'`  \\  `. `.     <<<<>>>>    \n' +'      \'/\\`         /  .\' .\'.\'/|..|\\`.`. `.  \\         \'/\\`      \n' +'      (())        `  /  / .\'| |||| |`. \\  \\  \'        (())      \n' +'       /\\          ::_.\' .\' /| || |\\ `. `._::          /\\       \n' +'      //\\\\           \'``.\' | | || | | `.\'\'`           //\\\\      \n' +'      //\\\\             .` .` | || | \'. \'.             //\\\\      \n' +'      //\\\\                `  | `\' |  \'                //\\\\      \n' +'      \\\\//                                            \\\\//      \n' +'       \\/                    Etc.End                   \\/       ')const activationVisible = ref<boolean>(false)const state = reactive({activationCode: '',message: '',pass: false})const activation = () => {state.message = ''appStore.userModule.sendActivate(state.activationCode).then(ts => {if (ts) {state.message = '激活成功!'state.pass = truesetTimeout(() => {activationVisible.value = false// 激活成功后向主程序发送命令 生成激活文件appStore.userModule.activationSuccessful(state.activationCode)router.push({path: '/'})}, 2 * 1000);} else {state.pass = falsestate.message = '无效激活码!'}})}const activationCodeFocus = () => {state.pass = falsestate.message = ''}const openDialog = () => {activationVisible.value = true;state.message = '';}return {...toRefs(state),activation,activationCodeFocus,text,openDialog,activationVisible,}}
})
</script><style scoped lang="scss">
</style>

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

<template><div style="display: flex;justify-content: center;align-items: center;width: 100%;height: 100vh;"><div style="font-size: 100px;">首页</div></div>
</template>

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并且重启项目

<template><Header /><router-view />
</template><script lang="ts">
import {defineComponent, nextTick, onMounted} from "vue";
import Header from '@/components/header/index.vue'
import appStore from "@/pinia";
export default defineComponent({components: {Header},setup() {onMounted(() => {nextTick(async () => {const data = await appStore.userModule.sendActivate('')})})}
})
</script><style>
html, body, #app {height: 100%;width: 100%;margin: 0;padding: 0;
}
</style>

五、效果浏览

1、无激活

2、激活

3、激活后重启项目

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

 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 


http://www.ppmy.cn/news/260041.html

相关文章

Python爬取广州、深圳、河源、惠州四个城市天气数据,并作数据可视化

本文爬取的网站是天气,天气预报查询,24小时,今天,明天,未来一周7天,10天,15天,40天查询_2345天气王 1.爬取广惠河深2022-2024年的数据 import requests # 发送请求要用的模块 需要额外安装的 import parsel import csvf open(广-惠-河-深天气.csv, modea, encodingut…

Spring面试题(基础篇)

目录 一、Spring框架概述 1、什么是Spring&#xff1f; 2、spring优点有哪些&#xff1f; 二、IOC与DI 3、你知道getBean方法的有几种重载方式吗&#xff1f; 4、Spring有几种依赖注入方式&#xff1f; 三、Spring创建对象 5、Spring创建对象有几种方式&#xff1f; 6…

装修注意事项

1.漏电保护器、空气开关&#xff0c;买你能力范围内能买到的最好的。 2.插座、开关一定要自己买&#xff0c;买名牌的中高端的——西门子、TCL一样会出专门供工装渠道的低端插座开关&#xff0c;你以为质量会多好&#xff1f;市场上还有大量的仿冒产品&#xff0c;你指望装修队…

企业纷纷盯上“成套智慧家电”,这会是一片新蓝海吗?

文 | 曾响铃 来源 | 科技向令说&#xff08;xiangling0815&#xff09; 刚刚过去的中国家电及消费电子博览会&#xff08;AWE2021&#xff09;有别样的意义&#xff0c;一方面因为去年未能如期举办&#xff0c;厂商经过两年的沉淀&#xff0c;“拿出手”的东西普遍比较惊艳&a…

只需8个步骤,即可在线创建优秀的帮助中心

创建一个优秀的帮助中心可以帮助企业更好地向用户传递信息&#xff0c;提高用户满意度和忠诚度。在今天的数字化时代&#xff0c;许多企业都已经开始意识到了帮助中心的重要性。 8个步骤&#xff0c;帮助您在线创建一个优秀的帮助中心 步骤1&#xff1a;明确目标和用户需求 …

python怎么搭建免费代理IP池,免费代理IP适合爬虫工作吗

Python可以使用一些第三方库和工具来搭建免费代理IP池。简单来说&#xff0c;搭建代理IP池的步骤如下&#xff1a; 1. 获取代理IP&#xff1a;从一些免费或付费代理IP网站上爬取并验证IP地址和端口信息。 2. 验证代理IP&#xff1a;使用代理IP访问一些网站或服务&#xff0c;验…

css自定义属性/css变量

css自定义属性/css变量由自定义属性标记设定值&#xff0c;由 var() 函数来获取值属性名需要以两个减号&#xff08;–&#xff09;开始 全局变量&#xff1a;在:root伪类上定义自定义属性&#xff0c;可以在 HTML 文档中全局应用它 局部变量&#xff1a;在选择器中定义的变量…

研华工控机设置断电自启动

选择Chipset 在选择 South Bridge进去 最后restort AC ....... off 改成 on