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

news2025/4/19 8:12:14

一、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 = 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))
                    } else {
                        reject(false);
                    }
                } catch (e) {
                    // 首先去查找激活文件是否存在 如果不存在的话就使用页面传入的激活码进行校验
                    if (str) {
                        that.is_activation = false
                        return 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 = 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) {
        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-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。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/619539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux5.1 LVS负载均衡群集

文章目录 计算机系统5G云计算第一章 LINUX LVS负载均衡群集一、LVS概述1.群集的含义2.群集的特点3.扩展服务器的方式4.群集的类型5.负载均衡的结构6.负载均衡集群工作模式分析 二、LVS-NAT 的部署1.关于 LVS 虚拟服务器2.LVS的负载调度算法3.使用 ipvsadm 工具 三、NAT模式 LVS…

ChatGPT助力码上行动:零基础学会Python编程

摘要&#xff1a; Python编程作为一种简洁、易学且功能强大的编程语言&#xff0c;正逐渐成为初学者进入编程领域的首选。然而&#xff0c;对于零基础的学习者来说&#xff0c;学习编程仍然存在一定的挑战。本文将介绍如何利用ChatGPT的强大语言生成能力&#xff0c;助力零基础…

元宇宙应用领域-社交

社交是一个古老的话题&#xff0c;人类从最开始的结群&#xff0c;到后来的部落&#xff0c;再到如今的网络社交&#xff0c;可以说人类的社交方式经历了漫长的演化过程。 随着互联网的普及和网络社交方式的不断发展&#xff0c;社交对于人类而言越来越重要。人们在网上不仅可…

SQL语句之DQL语言(二)(多表查询)

准备工作&#xff1a;创建表&#xff0c;添加数据 -- 部门管理 create table tb_dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null comment 创建时间,update_time datetime…

新招了个从腾讯拿38K离职的测试大佬,让我见识到了什么才是测试界的天花板

现在招个会几年工作经验还会自动化测试的测试工程师真是难呀&#xff0c;10个里面有8个写了会自动化&#xff0c;但一问就是三不知 5年测试工作经验&#xff0c;技术应该是能达到资深测试的水准&#xff0c;即不仅能熟练地开发业务&#xff0c;而且还能熟悉项目的开发&#xff…

【数据结构每日一题】栈——中心对称链

[数据结构习题]栈——中心对称链 &#x1f449;知识点导航&#x1f48e;&#xff1a;【数据结构】栈和队列 &#x1f449;[王道数据结构]习题导航&#x1f48e;&#xff1a; p a g e 70.4 page70.4 page70.4 本节为栈和链表综合练习题 题目描述&#xff1a; &#x1f387;思路…

【论文阅读】dreambooth

简介 目标&#xff1a;subject-driven generation&#xff0c;针对特定物体的图像生成&#xff0c;仅使用少量目标主体图像&#xff0c;dreambooth可以在prompt的指导下生成大量目标主体在不同场景下的图像。例如下图中小狗&#xff0c;我们给定的set就是左侧的input images&a…

Matplotlib - 绘制 高亮显示的饼图 (Highlight Pie Chart) 函数源码

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131089501 饼图 (Pie Chart) 是一种圆形统计图&#xff0c;被分割成片用于表示数值间的比例关系。每个切片的弧长以及相应的中心角和面积…

【QQ聊天界面-创建自定义Cell Objective-C语言】

一、我们刚才说到这个地方,我们说,用系统的单元格,是不是不够用吧, 1.那么这个时候,我们就要、需要自定义单元格 自定义单元格,我们就新建一个类,继承自UITableViewCell 来,写一下, 那么,这个时候,应该在哪个文件夹下,去新建类啊, 是不是在Views下面吧, 因为…

一文详解!Robot Framework Selenium UI自动化测试入门篇

目录 前言&#xff1a; 自动化框架的选择 测试环境的搭建 导入Selenium2Library包 关键字是什么&#xff1f; 创建测试用例 前言&#xff1a; 自动化测试的重要性越来越受到人们的重视&#xff0c;因为它可以提高测试效率、降低测试成本并减少人为错误的出现。为了满足这…

软考A计划-电子商务设计师-模拟试题卷八

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Shiro自定义过滤器会执行两次?看我怎么给你解决

关注“Java架构栈”微信公众号&#xff0c;回复暗号【Java面试题】即可获取大厂面试题。 最近九哥的一个学生在使用自定义ShiroFilter处理JWT校验时&#xff0c;发现自己写的Filter在处理一次请求时会被执行两次。这个问题困扰了他一个下午都没有解决&#xff0c;最后不得不求…

新手小白如何入门学习CTF?【网络安全】

最近有很多新手小白私信我&#xff1a;如何学习CTF&#xff1f;新手小白应该怎么入门CTF&#xff1f;想打CTF&#xff0c;但是没有思路怎么办&#xff1f; 昨天下班之后&#xff0c;花了几个小时&#xff0c;整理了一下CTF学习的思路与方法&#xff0c;分享给大家&#xff0c;如…

用jprofiler来分析 jvm 堆 内存泄露,fullgc

jvm 命令和工具_个人渣记录仅为自己搜索用的博客-CSDN博客 堆太大? 方法1: 重新设置堆后,重启,复现. 方法2: 切割 split -b 1M heap.bin smallfilescp smallfile* usernamemac-host:/Users/username/cat smallfile* > heap.bin官网文档 JProfiler Help - HPROF snapshot…

【逃离】UniAccess

能看到这篇文章&#xff0c;说明你已经是老屁股了&#xff08;保命要紧&#xff09; 上面是UniAccess功能 你想要做的事情无非是三种 不顾后果强力卸载UniAccess期望只保留(内网)网络认证禁用UniAccess部分功能 第一种&#xff1a;直接卸载&#xff0c;这里不做说明了&#x…

开发轮子(一):全国省/市/区/街道三、四级联动

概述 本服务提供中国标准行政区划数据查询功能&#xff0c;支持&#xff1a; 1 . 全国省、市、区/县、乡镇/街道 四级行政区划数据&#xff1b; 2 . 支持三级区划&#xff08;省/市 - 区/县&#xff09;轮廓数据&#xff1b; 3 . 支持区划查询、省市区列表、查询子级区划等功能…

十六、立方体贴图(天空盒)

第一部分 概念&#xff1a; 1) 引用 OpenGL ES 立方体贴图本质上还是纹理映射&#xff0c;是一种 3D 纹理映射。立方体贴图所使的纹理称为立方图纹理&#xff0c;它是由 6 个单独的 2D 纹理组成&#xff0c;每个 2D 纹理是立方图的一个面。 立方图纹理的采样通过一个 3D 向量…

CodeGeeX 2.0版本重大升级:通过聊天对话的方式直接操作代码

CodeGeeX 2.0版本正式上线&#xff01;从命名上看这是一次大版本的升级。 上个月&#xff0c;CodeGeeX在VSCode和JetBrains IDEs的插件中&#xff0c;加入了智能问答&#xff08;Ask CodeGeeX&#xff09;功能&#xff0c;让用户可以在IDE中通过问答对话的方式解决技术问题。本…

Selenium自动化程序被检测为爬虫,怎么屏蔽和绕过

Selenium 操作被屏蔽 使用selenium自动化网页时&#xff0c;有一定的概率会被目标网站识别&#xff0c;一旦被检测到&#xff0c;目标网站会拦截该客户端做出的网页操作。 比如淘宝和大众点评的登录页&#xff0c;当手工打开浏览器&#xff0c;输入用户名和密码时&#xff0c…

docker 无法将“docker”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

&#x1f4a7; d o c k e r 无法将“ d o c k e r ”项识别为 . . . . . . \color{#FF1493}{docker 无法将“docker”项识别为......} docker无法将“docker”项识别为......&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页…