使用jsencrypt.js实现RSA加密和解密(长消息版)

<!doctype html>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <title>使用jsencrypt.js实现RSA加密和解密</title>
</head>
<body>
<script type="text/javascript" src="./jsencrypt.min.js"></script>
<script type="text/javascript">
    JSEncrypt.prototype.hexToB64 = function (h) {
        var b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        var i;
        var c;
        var ret = '';

        for (i = 0; i + 3 <= h.length; i += 3) {
            c = parseInt(h.substring(i, i + 3), 16);
            ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
        }

        if (i + 1 == h.length) {
            c = parseInt(h.substring(i, i + 1), 16);
            ret += b64map.charAt(c << 2);
        } else if (i + 2 == h.length) {
            c = parseInt(h.substring(i, i + 2), 16);
            ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
        }

        while ((ret.length & 3) > 0) {
            ret += '=';
        }

        return ret;
    };

    JSEncrypt.prototype.b64ToHex = function (s) {
        var b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        var ret = '';
        var i;
        var k = 0;
        var slop = 0;

        let intToChar = function (n) {
            return '0123456789abcdefghijklmnopqrstuvwxyz'.charAt(n);
        }

        for (i = 0; i < s.length; ++i) {
            if (s.charAt(i) == '=') {
                break;
            }

            var v = b64map.indexOf(s.charAt(i));
            if (v < 0) {
                continue;
            }

            if (k == 0) {
                ret += intToChar(v >> 2);
                slop = v & 3;
                k = 1;
            } else if (k == 1) {
                ret += intToChar((slop << 2) | (v >> 4));
                slop = v & 0xf;
                k = 2;
            } else if (k == 2) {
                ret += intToChar(slop);
                ret += intToChar(v >> 2);
                slop = v & 3;
                k = 3;
            } else {
                ret += intToChar((slop << 2) | (v >> 4));
                ret += intToChar(v & 0xf);
                k = 0;
            }
        }

        if (k == 1) {
            ret += intToChar(slop << 2);
        }

        return ret;
    }

    JSEncrypt.prototype.encryptUnicodeLong = function (string) {
        var k = this.getKey();
        var maxLength = ((k.n.bitLength() + 7) >> 3) - 11; // 明文允许的最大长度

        try {
            var subStr = '';
            var encryptedString = '';
            var subStart = 0;
            var subEnd = 0;
            var bitLen = 0;
            var tmpPoint = 0;

            for (var i = 0, len = string.length; i < len; i++) {
                var charCode = string.charCodeAt(i);

                if (charCode <= 0x007f) { // 单字节
                    bitLen += 1;
                } else if (charCode <= 0x07ff) { // 双字节
                    bitLen += 2;
                } else if (charCode <= 0xffff) { // 三字节
                    bitLen += 3;
                } else {
                    bitLen += 4;
                }

                if (bitLen > maxLength) {
                    subStr = string.substring(subStart, subEnd)
                    encryptedString += k.encrypt(subStr);
                    subStart = subEnd;
                    bitLen = bitLen - tmpPoint;
                } else {
                    subEnd = i;
                    tmpPoint = bitLen;
                }
            }

            subStr = string.substring(subStart, len)
            encryptedString += k.encrypt(subStr);

            return this.hexToB64(encryptedString);
        } catch (ex) {
            return false;
        }
    };

    JSEncrypt.prototype.decryptUnicodeLong = function (string) {
        var k = this.getKey();
        var maxLength = ((k.n.bitLength() + 7) >> 3) * 2;

        try {
            var hexString = this.b64ToHex(string);
            var decryptedString = '';
            var rexStr = '.{1,' + maxLength + '}';
            var rex = new RegExp(rexStr, 'g');
            var subStrArray = hexString.match(rex);

            if (subStrArray) {
                subStrArray.forEach(function (entry) {
                    decryptedString += k.decrypt(entry);
                });

                return decryptedString;
            }
        } catch (ex) {
            return false;
        }
    };


    //========== 公钥(用于加密) ==========//
    let publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzjGQT86U567mS2j3dzuf
Q6sDJJDGfnO9J8ESaiwvMChZ30zD/USypRlKSnX1d+bqTLFMoNEOK/bQmqZiiSk5
Fzek2IA78n4NsvorKfsc5Gje2RQGqG5kKMt1TfLg2cF8dOZ+8Q6FoJ6EB3Dd1KPU
f0duLcEXs+Am70CVeBs7aGYVUY2dtVlAVSK1mlUmYWNxRlk5V5oSdUaHN9vAzgiJ
Y844bbpP3XOCgT/Fc0sN+AVoGIrgAyGt1qWHTiXIbJQ0ocyFoaT/CCdP7ueqesJp
LCxupSqoS0SdNh3JpbLz4ZnHGug7uP1mqwkGQACAJfYE6X7fNRO+BIaVJRGCMBcm
jwIDAQAB
-----END PUBLIC KEY-----`;

    //========== 私钥(用于解密) ==========//
    let privateKey = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOMZBPzpTnruZL
aPd3O59DqwMkkMZ+c70nwRJqLC8wKFnfTMP9RLKlGUpKdfV35upMsUyg0Q4r9tCa
pmKJKTkXN6TYgDvyfg2y+isp+xzkaN7ZFAaobmQoy3VN8uDZwXx05n7xDoWgnoQH
cN3Uo9R/R24twRez4CbvQJV4GztoZhVRjZ21WUBVIrWaVSZhY3FGWTlXmhJ1Roc3
28DOCIljzjhtuk/dc4KBP8VzSw34BWgYiuADIa3WpYdOJchslDShzIWhpP8IJ0/u
56p6wmksLG6lKqhLRJ02HcmlsvPhmcca6Du4/WarCQZAAIAl9gTpft81E74EhpUl
EYIwFyaPAgMBAAECggEAR5X7lUmSdvFI8QtrRxEDFTotKCe/Ui2akU+9tfDLHTwV
H6qGLMsJ/rnOChXz+AHKfH/dq8OI2Qiimd6EPTx7nqzp5WR365OJ7AZgr/2HpWEn
ZVRHj3hr+6HPgxV8rP042Vkg3038ZKxECFVOHsIWR24kOWxdb0y2F8BjZESIFpEi
IRN9Xb77C2yCOUX/yoyMgjkKM/Z+cThM5wQ0nR1wZHDl4CE91UxhdDYBQz5uzoDV
qZerHT2Kmy5fUBRGxJdSujN7qyGgf9FarNkfBYAII97StIS512ZuLCwzmEddXp4i
gNlsla0eqCxQvAqOoGm8Fp/N0PCyfR7fJexck3L1kQKBgQD+SvKklvp055BpVB2n
IsJHpPd43zXxxX+k8eQSKhGIy+6eO53xapQDZiB/4OVfGnY+xPF4z8nLxCWd9C3S
K6X/OzQTC7c4Y4a9L/P2NKissuqsxzMf2jGPCUBIGKcBZ9eizOGNbdhJ/HvXQY7r
CxyRaeOqU6om1DKPHBkFsXPfGQKBgQDPk/Kxugnzgv+4b2TWwxY7Ot2LcWszPkEp
RsbBLdCS08h9GYOlC1kGMXZFv8QGO3R+e4mZMOIVhwSadrhfGwvlqDumDaORF+MH
ZZAe6ARRX71KKTjGZqcX0nImLI5o1s/YMK8BkCE6ArYVPFspk68Voun4wbNcO/U9
aoNsK9Vv5wKBgQDDW3jFkWegYDXFdWXCfSWcPNQR/AlJUF0brul0OvV1jpYm4c4Z
JbPIWLEnDPOp+H5XAp4wHhH9hRcRHgIFsJq6VhVPfHSp0Ww1850MzK+43UsEqZRR
KCNiq8zClo3Wupwi6httt7GuRVYurKLLV6H+5MaOl+/kHKkq4H8orIdEIQKBgGng
uOXWUsUWiID2sKSqlWhYujAqBdf5ZRs8spxOVhjOVXEZ1oAUra/vArjI+5+CLAVn
1eOBf5AjckGnVJuOHB9kFCi6xDd5y582OrDI/4rSHqb5J7BrI8eO3BKEn47yIsnO
6zUM4yXHxEBIrOckISYUFut/QZFGM+zDq409Pnz5AoGAXqkbybLPznaumBngpY25
BOnBxF02hWxiWlREsXbqfpa6WU50BLXFxknP+91QYwOHafZpdjm7LL8GJ7H5vmyK
CLhxY5fZejlze0eMa8A07JWBH9F4cBamI9NbmatfmhlvLjPaRcNaoyuk1W8LuL9Z
im+u/8E9Zm1atbpr7lP33YQ=
-----END PRIVATE KEY-----`;


    // 要加密的消息
    let message = '北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。';

    let jse = new JSEncrypt();
    let startTime = 0;
    let endTime = 0;

    //========== 使用公钥加密 ==========//
    jse.setKey(publicKey);
    startTime = new Date().getTime();
    let ciphertext = jse.encryptUnicodeLong(message); // 注:相同消息每次加密的结果都是不同的
    endTime = new Date().getTime();
    console.log('消息密文:%s', ciphertext); // 消息密文:mZ/Y7DBPR5p+oNW+aYs0nj0eX+KwDJquCDOOoBjQABqAA89BrEtbCXpSOeFf2vtcEmnxsZAYa……
    console.log('加密用时:%d毫秒', endTime - startTime); // 加密用时:8毫秒

    //========== 使用私钥解密 ==========//
    jse.setKey(privateKey);
    startTime = new Date().getTime();
    let plaintext = jse.decryptUnicodeLong(ciphertext);
    endTime = new Date().getTime();
    console.log('消息明文:%s', plaintext); // 消息明文:北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山……
    console.log('解密用时:%d毫秒', endTime - startTime); // 解密用时:66毫秒

    //========== 总结 ==========//
    // 1、超长消息的加解密会非常耗时(尤其是解密),所以强烈建议使用Worker创建子线程来执行加解密运算,避免页面发生阻塞。
</script>
</body>
</html>



  下面是把加解密运算放在子线程里执行的代码:

//========== 由主线程执行的代码 ==========//
if (typeof window !== 'undefined') {
    // 获取当前JavaScript脚本URL,并创建子线程执行当前JavaScript脚本里的子线程代码
    let worker = new Worker(document.scripts[document.scripts.length - 1].src);

    worker.onerror = function (event) {
        console.log('子线程出错辣~~~', event);
    };

    let task = 2; // 任务数量(只有加密和解密两个任务,完成一个就自减一)

    // 接收子线程消息
    worker.onmessage = function (event) {
        let data = event.data;

        // 收到子线程消息:加密成功
        if (data.action === 'encrypt') {
            console.log('消息密文:%s', data.ciphertext);
            task--;
        }

        // 收到子线程消息:解密成功
        if (data.action === 'decrypt') {
            console.log('消息明文:%s', data.plaintext);
            task--;
        }

        // 任务已全部完成,可以销毁子线程
        if (task === 0) {
            worker.terminate();
        }
    };

    // 向子线程发送消息(加密)
    let plaintext = '北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。';
    worker.postMessage({action: 'encrypt', plaintext: plaintext});

    // 向子线程发送消息(解密)
    let ciphertext = 'M01IMhuL/GRZpAlf9sUaiJm8oWIpJNc4pbzKLShuw3HSm43dRZ4/bkKuES2mgARCuEMktinUqcCJv6seGNrehiwV+CwXDnL0+PjKvnFT3Y2/wc64D6Nq5Xi9e9HfPmQUMBFMWhXBnUjycwmkR92Bnr55+qCHIgJPi++xQtLponX6Z0QD9PlOPNxtKSzo+tW7tr1Nn3ADmp6QHzbiSL1alNjwzUfn/DwhjivaAVBNXg6IYUPa/HCsNwWfRzJCULND8pa7J2XFLGyfMTL6UJCAL1p5kCIG2v3ml9RJI8LfzupvyalNGNOp9ZNo7kRk2zKAqa17lk7GKRrccb/FAxUaKgeo8QquOBw7972lsHLLdmRlPzRPU3O0LdJoY05TOL1ytBD5s5Og7UsIpkBORqAGZICcs3BqvLEFOB7uSSESPvi8lpnHXLPzJw4JZ57/ye/w2V6qWuFtZU6+xspOjEVtCXTXc4WPI3n8ExAUy7+DmzDyFPRzbM6oj59p4I6kSmg8QbBjy03XTEG393crO6ghZZaZPDfm2wgPkZTyCyz5Fjc033nrssLZ6Jir0paj9RTCB3AM2kNW+rEQ2bbJ7YwRianl9+9qipMKTncoSNEjs+HYMivcLSDTcYrZQFBz0V/GJ1A5lopZCen2OMR9EQHzQXEjvNazqdkUpBREWTIfiBo=';
    worker.postMessage({action: 'decrypt', ciphertext: ciphertext});
}


//========== 由子线程执行的代码 ==========//
if (typeof window === 'undefined') {
    var window = {}; // JSEncrypt库会用到window对象,故这里创建一个,需要说明的是这个window对象只是一个普通的对象,
                     // 并不是浏览器内置的window对象,所以它没有浏览器内置window对象的属性和方法。

    importScripts('jsencrypt.min.js'); // 引入JSEncrypt库,在页面通过<script>引入是无效的,因为那是主线程的,而这里是子线程


    window.JSEncrypt.prototype.hexToB64 = function (h) {
        var b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        var i;
        var c;
        var ret = '';

        for (i = 0; i + 3 <= h.length; i += 3) {
            c = parseInt(h.substring(i, i + 3), 16);
            ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
        }

        if (i + 1 == h.length) {
            c = parseInt(h.substring(i, i + 1), 16);
            ret += b64map.charAt(c << 2);
        } else if (i + 2 == h.length) {
            c = parseInt(h.substring(i, i + 2), 16);
            ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
        }

        while ((ret.length & 3) > 0) {
            ret += '=';
        }

        return ret;
    };

    window.JSEncrypt.prototype.b64ToHex = function (s) {
        var b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        var ret = '';
        var i;
        var k = 0;
        var slop = 0;

        let intToChar = function (n) {
            return '0123456789abcdefghijklmnopqrstuvwxyz'.charAt(n);
        }

        for (i = 0; i < s.length; ++i) {
            if (s.charAt(i) == '=') {
                break;
            }

            var v = b64map.indexOf(s.charAt(i));
            if (v < 0) {
                continue;
            }

            if (k == 0) {
                ret += intToChar(v >> 2);
                slop = v & 3;
                k = 1;
            } else if (k == 1) {
                ret += intToChar((slop << 2) | (v >> 4));
                slop = v & 0xf;
                k = 2;
            } else if (k == 2) {
                ret += intToChar(slop);
                ret += intToChar(v >> 2);
                slop = v & 3;
                k = 3;
            } else {
                ret += intToChar((slop << 2) | (v >> 4));
                ret += intToChar(v & 0xf);
                k = 0;
            }
        }

        if (k == 1) {
            ret += intToChar(slop << 2);
        }

        return ret;
    }

    window.JSEncrypt.prototype.encryptUnicodeLong = function (string) {
        var k = this.getKey();
        var maxLength = ((k.n.bitLength() + 7) >> 3) - 11; // 明文允许的最大长度

        try {
            var subStr = '';
            var encryptedString = '';
            var subStart = 0;
            var subEnd = 0;
            var bitLen = 0;
            var tmpPoint = 0;

            for (var i = 0, len = string.length; i < len; i++) {
                var charCode = string.charCodeAt(i);

                if (charCode <= 0x007f) { // 单字节
                    bitLen += 1;
                } else if (charCode <= 0x07ff) { // 双字节
                    bitLen += 2;
                } else if (charCode <= 0xffff) { // 三字节
                    bitLen += 3;
                } else {
                    bitLen += 4;
                }

                if (bitLen > maxLength) {
                    subStr = string.substring(subStart, subEnd)
                    encryptedString += k.encrypt(subStr);
                    subStart = subEnd;
                    bitLen = bitLen - tmpPoint;
                } else {
                    subEnd = i;
                    tmpPoint = bitLen;
                }
            }

            subStr = string.substring(subStart, len)
            encryptedString += k.encrypt(subStr);

            return this.hexToB64(encryptedString);
        } catch (ex) {
            return false;
        }
    };

    window.JSEncrypt.prototype.decryptUnicodeLong = function (string) {
        var k = this.getKey();
        var maxLength = ((k.n.bitLength() + 7) >> 3) * 2;

        try {
            var hexString = this.b64ToHex(string);
            var decryptedString = '';
            var rexStr = '.{1,' + maxLength + '}';
            var rex = new RegExp(rexStr, 'g');
            var subStrArray = hexString.match(rex);

            if (subStrArray) {
                subStrArray.forEach(function (entry) {
                    decryptedString += k.decrypt(entry);
                });

                return decryptedString;
            }
        } catch (ex) {
            return false;
        }
    };


    //========== 公钥(用于加密) ==========//
    let publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzjGQT86U567mS2j3dzuf
Q6sDJJDGfnO9J8ESaiwvMChZ30zD/USypRlKSnX1d+bqTLFMoNEOK/bQmqZiiSk5
Fzek2IA78n4NsvorKfsc5Gje2RQGqG5kKMt1TfLg2cF8dOZ+8Q6FoJ6EB3Dd1KPU
f0duLcEXs+Am70CVeBs7aGYVUY2dtVlAVSK1mlUmYWNxRlk5V5oSdUaHN9vAzgiJ
Y844bbpP3XOCgT/Fc0sN+AVoGIrgAyGt1qWHTiXIbJQ0ocyFoaT/CCdP7ueqesJp
LCxupSqoS0SdNh3JpbLz4ZnHGug7uP1mqwkGQACAJfYE6X7fNRO+BIaVJRGCMBcm
jwIDAQAB
-----END PUBLIC KEY-----`;

    //========== 私钥(用于解密) ==========//
    let privateKey = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOMZBPzpTnruZL
aPd3O59DqwMkkMZ+c70nwRJqLC8wKFnfTMP9RLKlGUpKdfV35upMsUyg0Q4r9tCa
pmKJKTkXN6TYgDvyfg2y+isp+xzkaN7ZFAaobmQoy3VN8uDZwXx05n7xDoWgnoQH
cN3Uo9R/R24twRez4CbvQJV4GztoZhVRjZ21WUBVIrWaVSZhY3FGWTlXmhJ1Roc3
28DOCIljzjhtuk/dc4KBP8VzSw34BWgYiuADIa3WpYdOJchslDShzIWhpP8IJ0/u
56p6wmksLG6lKqhLRJ02HcmlsvPhmcca6Du4/WarCQZAAIAl9gTpft81E74EhpUl
EYIwFyaPAgMBAAECggEAR5X7lUmSdvFI8QtrRxEDFTotKCe/Ui2akU+9tfDLHTwV
H6qGLMsJ/rnOChXz+AHKfH/dq8OI2Qiimd6EPTx7nqzp5WR365OJ7AZgr/2HpWEn
ZVRHj3hr+6HPgxV8rP042Vkg3038ZKxECFVOHsIWR24kOWxdb0y2F8BjZESIFpEi
IRN9Xb77C2yCOUX/yoyMgjkKM/Z+cThM5wQ0nR1wZHDl4CE91UxhdDYBQz5uzoDV
qZerHT2Kmy5fUBRGxJdSujN7qyGgf9FarNkfBYAII97StIS512ZuLCwzmEddXp4i
gNlsla0eqCxQvAqOoGm8Fp/N0PCyfR7fJexck3L1kQKBgQD+SvKklvp055BpVB2n
IsJHpPd43zXxxX+k8eQSKhGIy+6eO53xapQDZiB/4OVfGnY+xPF4z8nLxCWd9C3S
K6X/OzQTC7c4Y4a9L/P2NKissuqsxzMf2jGPCUBIGKcBZ9eizOGNbdhJ/HvXQY7r
CxyRaeOqU6om1DKPHBkFsXPfGQKBgQDPk/Kxugnzgv+4b2TWwxY7Ot2LcWszPkEp
RsbBLdCS08h9GYOlC1kGMXZFv8QGO3R+e4mZMOIVhwSadrhfGwvlqDumDaORF+MH
ZZAe6ARRX71KKTjGZqcX0nImLI5o1s/YMK8BkCE6ArYVPFspk68Voun4wbNcO/U9
aoNsK9Vv5wKBgQDDW3jFkWegYDXFdWXCfSWcPNQR/AlJUF0brul0OvV1jpYm4c4Z
JbPIWLEnDPOp+H5XAp4wHhH9hRcRHgIFsJq6VhVPfHSp0Ww1850MzK+43UsEqZRR
KCNiq8zClo3Wupwi6httt7GuRVYurKLLV6H+5MaOl+/kHKkq4H8orIdEIQKBgGng
uOXWUsUWiID2sKSqlWhYujAqBdf5ZRs8spxOVhjOVXEZ1oAUra/vArjI+5+CLAVn
1eOBf5AjckGnVJuOHB9kFCi6xDd5y582OrDI/4rSHqb5J7BrI8eO3BKEn47yIsnO
6zUM4yXHxEBIrOckISYUFut/QZFGM+zDq409Pnz5AoGAXqkbybLPznaumBngpY25
BOnBxF02hWxiWlREsXbqfpa6WU50BLXFxknP+91QYwOHafZpdjm7LL8GJ7H5vmyK
CLhxY5fZejlze0eMa8A07JWBH9F4cBamI9NbmatfmhlvLjPaRcNaoyuk1W8LuL9Z
im+u/8E9Zm1atbpr7lP33YQ=
-----END PRIVATE KEY-----`;

    let jse = new window.JSEncrypt(); // 创建JSEncrypt对象


    // 接收主线程消息
    self.onmessage = function (event) {
        let data = event.data;

        //========== 使用公钥加密 ==========//
        if (data.action === 'encrypt') {
            jse.setKey(publicKey);
            let ciphertext = jse.encryptUnicodeLong(data.plaintext); // 注:相同消息每次加密的结果都是不同的
            self.postMessage({action: 'encrypt', ciphertext: ciphertext}); // 向主线程发送消息(返回密文)
            return;
        }

        //========== 使用私钥解密 ==========//
        if (data.action === 'decrypt') {
            jse.setKey(privateKey);
            let plaintext = jse.decryptUnicodeLong(data.ciphertext);
            self.postMessage({action: 'decrypt', plaintext: plaintext}); // 向主线程发送消息(返回明文)
            return;
        }
    };
}

Copyright © 2024 码农人生. All Rights Reserved