admin 管理员组文章数量: 887021
一、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)
* @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<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) {
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页面
<template>
<div>
<div style="width: 100%;height: 100%;">
<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)</span>
</el-footer>
</div>
<el-dialog v-model="activationVisible">
<el-input
v-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 = true
setTimeout(() => {
activationVisible.value = false
// 激活成功后向主程序发送命令 生成激活文件
appStore.userModule.activationSuccessful(state.activationCode)
router.push({
path: '/'
})
}, 2 * 1000);
} else {
state.pass = false
state.message = '无效激活码!'
}
})
}
const activationCodeFocus = () => {
state.pass = false
state.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。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
本文标签: 第六章 机制 Node Electron license
版权声明:本文标题:第六章 Electron|Node 实现license激活机制 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1716173859h669456.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论