爬虫案例-使用Session登录某知名网站(JS逆向AES-CBC加密+MD5加密)

news2024/12/28 19:55:10

总体概览:使用Session登录该网站,其中包括对password参数进行js逆向破解

                (涉及加密:md5加密+AES-CBC加密)

难度:两颗星

目标网址:aHR0cHM6Ly93d3cuZnhiYW9nYW8uY29tLw==

下面文章将分为四个部分:

        1、定位主体加密函数,进行断点

        2、分析整体加密流程

        3、对JS代码进行扣取、补充、重写

        4、使用Session进行登录

运行效果展示:

JS逆向运行截图:

Session登陆成功截图:

正文开始: 

第一步、定位主体加密函数,进行断点

首先进入网站,点击登陆后进行抓包,很明显可以看出这里的密码(data参数)被进行了加密。

        由于data参数比较常见不太好搜索,因此我们对url的接口byPhoneNumber进行搜索

        搜索一共只有三个结果,比较少就都点进去看看,发现第一个很明显就是加密的主体函数并且 l 为加密的数据,直接对 l 参数进行断点后再次点击登陆,确定这个就是我们所需要的函数

第二步、分析整体加密流程

var o = e.password
, s = e.mobile
, a = A.dnGetter()
, l = D().ne("".concat(a).concat(o), _()("".concat(s.slice(3)).concat(a)));

直接看代码的话初步得出以下结论:

o为密码,s为账号,a疑似一个时间戳(这个后面再去验证),l 为加密后的一串数据

其中 l 为核心,下面对 l 进行分析:

1、其中可以看出括号内前半截是a与o的一个简单的拼接

2、后半截的话是先对密码部分截取第三位数之后的数据并与a进行拼接,并通过 _() 函数进行处理,发现 _() 是对拼接后的数据进行了md5的加密

3、在括号内的数据处理完成后调用D函数下的ne方法再进行一次处理

 

  得到这些信息后开始扣取JS代码并补充缺少的函数

第三步、对JS代码进行扣取、补充、重写

步骤1:对a参数进行补充

首先将主体的加密函数复制下来写到自己定义的一个方法里,文中可以看出需要传入e参数(账号密码信息),下面是主体函数的代码:

var get_password = function (e){
        var o = e.password
      , s = e.mobile
      , a = A.dnGetter()
      , l = D().ne("".concat(a).concat(o), _()("".concat(s.slice(3)).concat(a)));
        return l
}
var e = {
    "mobile": "17688888888",
    "password": "Wu123123123"
}
console.log(get_password(e))

很明显这里缺少了 A.dnGetter() 函数,找到这个函数点击进入发现它是一个名为 ot 的函数,写入JS代码中,并将原来的 A.dnGetter() 改写为 ot() 

 

 点击运行,发现缺少参数c,在此设置断点,运行了几次之后发现c为固定值false,因此我们直接令他为false,同时我们发现这句话还缺少了 window.D 参数 ,运行发现 这个也是固定值,补充完后运行一下结果没毛病。

 

 

window = global
window.D = 1
var ot = function() {
    return false || "number" !== typeof window.D ? +String(Date.now()).substr(0, 10) : +String(Date.now()).substr(0, 10) + window.D
}

 接下来就是我们的重头戏了!!

步骤2:对D.ne函数进行补充

首先是进入D函数下的ne方法,进入发现这里是一个很明显的AES-CBC加密,将D改为一个对象,将ne方法放入D中,并将原本的D().ne() 改写为 D.ne     (这里有两种写法,我个人更喜欢写成D.ne一点看着会简单一些,如果想要 D().ne()的写法的话也可以,都放在下面了)

 

 写法1:

 写法2:

 再次运行发现缺少了对象,由于这里的格式与AES加密一模一样,因此在这里我将o替换成了JavaScript中的 crypto-js 库,这是一个经常用于加解密的库,调用前需要安装 jsdom

安装及使用方法可以看我上一篇文章所讲的:AES加密和解密详解_浩·的博客-CSDN博客

let CryptoJS = require('crypto-js');		// 调用crypto-js 模块
let D = {
        ne: function(e, t) {
        return CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(e), CryptoJS.enc.Utf8.parse(t.substr(0, 32)), {
            iv: CryptoJS.enc.Utf8.parse(t.substr(-16)),
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }).ciphertext.toString().toUpperCase()
    }
}

 步骤3:对 _() 函数进行补充与改写

前面分析过了在这下面的部分其实是将_()后面的数据进行了md5加密,会改写JS代码的或者使用python替代的可以直接跳过这一步不看。下面这部分主要还是练习一下对JS的逆向能力。对md5加密函数进行还原操作。

这里有一个小坑,将鼠标移到 _() 上后发现调用的是a函数,需要将a先展开再进入a中。

可以看见这里传入了我们前面所讲的后半截数据,因此可以将原本的

_()(后半截数据)  改写为 _(后半截数据) 

同时为了美观,我们将 _  函数改名为exports,具体改写后的代码如下:

exports = function(e, n) {
    if (void 0 === e || null === e)
        throw new Error("Illegal argument " + e);
    var r = t.wordsToBytes(i(e, n));
    return n && n.asBytes ? r : n && n.asString ? a.bytesToString(r) : t.bytesToHex(r)
}
var get_password = function (e){
        var o = e.password
      , s = e.mobile
      , a = ot()
      , l = D.ne("".concat(a).concat(o), exports(("".concat(s.slice(3)).concat(a))));
        return l
}

再次运行,发现对象 t 及其附带的函数wordsToBytes、bytesToHex都未被定义,给t下断点,进入t中。刚好发现我们想要的两个函数都在里面,直接带走。新建一个对象t,并放入这两个函数。

 

let t = {
    wordsToBytes: function(e) {
        for (var t = [], n = 0; n < 32 * e.length; n += 8)
            t.push(e[n >>> 5] >>> 24 - n % 32 & 255);
        return t
    },
    bytesToHex: function(e) {
        for (var t = [], n = 0; n < e.length; n++)
            t.push((e[n] >>> 4).toString(16)),
            t.push((15 & e[n]).toString(16));
        return t.join("")
    }
}

 再次运行再 t 的旁边还缺少了个 i  

下面对 i 进行补充,进入 i 并打下断点,将i先全扣下来再运行

这里报错 r 未被定义,这个比较明显,因为r就在上面,可以看见 r 为一个对象,包含了两个方法

 

 

 

可以看见 r 调用的utf8就在这里,同时utf8的两个方法还用到了下面的bin,因此在这我们直接扣过去 并新建一个 r 对象保存。由于单个单词 t 容易造成重复,我们这里对 对象t改了一下名字,最后再 i 函数中调用

 

 再次运行,缺少了t.bytesToWords,回到刚刚的地方继续重复操作,定位过去发现这里是刚刚扣 t 对象的地方,我们只需要将新的给补充进去

 

 

 然后重复,运行、下断点、进入函数,发现v函数是再上面被定义的,

我们进入定义它的地方 i.ff 函数中,顺路吧剩下的四个一起扣下来

 

 继续重复刚刚的操作扣 t.endian([l, s, f, d]) ,发现这里还调用了上面两个函数,一起带走。

要注意一下这里名字变了,扣过去的时候要改回来,并且把下面的 n.rotl 也改成 t.rotl

 

 

 改完之后发现这里有个bug,如果要将n改为t的话这就与上面的for 循环中的t变量冲突了,因此我们将for循环的变量改为其他的。 于是这里改为图二。

 再次点击运行,发现已经大功告成啦!!!!!

 

到这里逆向部分就已经完成了,下面进行登录操作

第四步、使用Session进行登录

查看登录发送的包,发现这里有一个时间戳,根据逆向时候知道的这个时间戳需要用来加密,因此我们需要将这个时间戳与JS的时间戳保持一直。所以我们需要在python上生成时间戳,然后发送给JS,对此我们还要将JS的时间戳改为一个传入的参数,即传入a(时间戳参数)

// 主函数改写后:
var get_pas_word = function(e,a){
    var o = e.password
      , s = e.mobile
      // , a = ot()
    var l = D.ne("".concat(a).concat(o), aaa(("".concat(s.slice(3)).concat(a))));
    return l
}
var e_dic = {
    "mobile": "17836797789",
    "password": "123wu123123"
}
a = 1686207919
console.log(get_pas_word(e_dic,a))

全部代码:Session登录+JS逆向代码

JS加密代码:

window = global

var ot = function() {
    return false || "number" !== typeof window.D ? +String(Date.now()).substr(0, 10) : +String(Date.now()).substr(0, 10) + window.D
}

let CryptoJS = require('crypto-js');		// 调用crypto-js 模块
let D = {
        ne: function(e, t) {
        return CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(e), CryptoJS.enc.Utf8.parse(t.substr(0, 32)), {
            iv: CryptoJS.enc.Utf8.parse(t.substr(-16)),
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }).ciphertext.toString().toUpperCase()
    }
}

let r_obj = {
    utf8: {
        stringToBytes: function(e) {
            return r_obj.bin.stringToBytes(unescape(encodeURIComponent(e)))
        },
        bytesToString: function(e) {
            return decodeURIComponent(escape(r_obj.bin.bytesToString(e)))
        }
    },
    bin: {
        stringToBytes: function(e) {
            for (var t = [], n = 0; n < e.length; n++)
                t.push(255 & e.charCodeAt(n));
            return t
        },
        bytesToString: function(e) {
            for (var t = [], n = 0; n < e.length; n++)
                t.push(String.fromCharCode(e[n]));
            return t.join("")
        }
    }
}

i = function(e, n) {
    var r = r_obj.utf8
                e.constructor == String ? e = n && "binary" === n.encoding ? a.stringToBytes(e) : r.stringToBytes(e) : o(e) ? e = Array.prototype.slice.call(e, 0) : Array.isArray(e) || e.constructor === Uint8Array || (e = e.toString());
                for (var c = t.bytesToWords(e), u = 8 * e.length, l = 1732584193, s = -271733879, f = -1732584194, d = 271733878, p = 0; p < c.length; p++)
                    c[p] = 16711935 & (c[p] << 8 | c[p] >>> 24) | 4278255360 & (c[p] << 24 | c[p] >>> 8);
                c[u >>> 5] |= 128 << u % 32,
                c[14 + (u + 64 >>> 9 << 4)] = u;

                i._ff = function(e, t, n, r, o, a, i) {
                    var c = e + (t & n | ~t & r) + (o >>> 0) + i;
                    return (c << a | c >>> 32 - a) + t
                }
                ,
                i._gg = function(e, t, n, r, o, a, i) {
                    var c = e + (t & r | n & ~r) + (o >>> 0) + i;
                    return (c << a | c >>> 32 - a) + t
                }
                ,
                i._hh = function(e, t, n, r, o, a, i) {
                    var c = e + (t ^ n ^ r) + (o >>> 0) + i;
                    return (c << a | c >>> 32 - a) + t
                }
                ,
                i._ii = function(e, t, n, r, o, a, i) {
                    var c = e + (n ^ (t | ~r)) + (o >>> 0) + i;
                    return (c << a | c >>> 32 - a) + t
                }
                var v = i._ff
                  , m = i._gg
                  , h = i._hh
                  , y = i._ii;



                for (p = 0; p < c.length; p += 16) {
                    var g = l
                      , b = s
                      , x = f
                      , w = d;
                    l = v(l, s, f, d, c[p + 0], 7, -680876936),
                    d = v(d, l, s, f, c[p + 1], 12, -389564586),
                    f = v(f, d, l, s, c[p + 2], 17, 606105819),
                    s = v(s, f, d, l, c[p + 3], 22, -1044525330),
                    l = v(l, s, f, d, c[p + 4], 7, -176418897),
                    d = v(d, l, s, f, c[p + 5], 12, 1200080426),
                    f = v(f, d, l, s, c[p + 6], 17, -1473231341),
                    s = v(s, f, d, l, c[p + 7], 22, -45705983),
                    l = v(l, s, f, d, c[p + 8], 7, 1770035416),
                    d = v(d, l, s, f, c[p + 9], 12, -1958414417),
                    f = v(f, d, l, s, c[p + 10], 17, -42063),
                    s = v(s, f, d, l, c[p + 11], 22, -1990404162),
                    l = v(l, s, f, d, c[p + 12], 7, 1804603682),
                    d = v(d, l, s, f, c[p + 13], 12, -40341101),
                    f = v(f, d, l, s, c[p + 14], 17, -1502002290),
                    l = m(l, s = v(s, f, d, l, c[p + 15], 22, 1236535329), f, d, c[p + 1], 5, -165796510),
                    d = m(d, l, s, f, c[p + 6], 9, -1069501632),
                    f = m(f, d, l, s, c[p + 11], 14, 643717713),
                    s = m(s, f, d, l, c[p + 0], 20, -373897302),
                    l = m(l, s, f, d, c[p + 5], 5, -701558691),
                    d = m(d, l, s, f, c[p + 10], 9, 38016083),
                    f = m(f, d, l, s, c[p + 15], 14, -660478335),
                    s = m(s, f, d, l, c[p + 4], 20, -405537848),
                    l = m(l, s, f, d, c[p + 9], 5, 568446438),
                    d = m(d, l, s, f, c[p + 14], 9, -1019803690),
                    f = m(f, d, l, s, c[p + 3], 14, -187363961),
                    s = m(s, f, d, l, c[p + 8], 20, 1163531501),
                    l = m(l, s, f, d, c[p + 13], 5, -1444681467),
                    d = m(d, l, s, f, c[p + 2], 9, -51403784),
                    f = m(f, d, l, s, c[p + 7], 14, 1735328473),
                    l = h(l, s = m(s, f, d, l, c[p + 12], 20, -1926607734), f, d, c[p + 5], 4, -378558),
                    d = h(d, l, s, f, c[p + 8], 11, -2022574463),
                    f = h(f, d, l, s, c[p + 11], 16, 1839030562),
                    s = h(s, f, d, l, c[p + 14], 23, -35309556),
                    l = h(l, s, f, d, c[p + 1], 4, -1530992060),
                    d = h(d, l, s, f, c[p + 4], 11, 1272893353),
                    f = h(f, d, l, s, c[p + 7], 16, -155497632),
                    s = h(s, f, d, l, c[p + 10], 23, -1094730640),
                    l = h(l, s, f, d, c[p + 13], 4, 681279174),
                    d = h(d, l, s, f, c[p + 0], 11, -358537222),
                    f = h(f, d, l, s, c[p + 3], 16, -722521979),
                    s = h(s, f, d, l, c[p + 6], 23, 76029189),
                    l = h(l, s, f, d, c[p + 9], 4, -640364487),
                    d = h(d, l, s, f, c[p + 12], 11, -421815835),
                    f = h(f, d, l, s, c[p + 15], 16, 530742520),
                    l = y(l, s = h(s, f, d, l, c[p + 2], 23, -995338651), f, d, c[p + 0], 6, -198630844),
                    d = y(d, l, s, f, c[p + 7], 10, 1126891415),
                    f = y(f, d, l, s, c[p + 14], 15, -1416354905),
                    s = y(s, f, d, l, c[p + 5], 21, -57434055),
                    l = y(l, s, f, d, c[p + 12], 6, 1700485571),
                    d = y(d, l, s, f, c[p + 3], 10, -1894986606),
                    f = y(f, d, l, s, c[p + 10], 15, -1051523),
                    s = y(s, f, d, l, c[p + 1], 21, -2054922799),
                    l = y(l, s, f, d, c[p + 8], 6, 1873313359),
                    d = y(d, l, s, f, c[p + 15], 10, -30611744),
                    f = y(f, d, l, s, c[p + 6], 15, -1560198380),
                    s = y(s, f, d, l, c[p + 13], 21, 1309151649),
                    l = y(l, s, f, d, c[p + 4], 6, -145523070),
                    d = y(d, l, s, f, c[p + 11], 10, -1120210379),
                    f = y(f, d, l, s, c[p + 2], 15, 718787259),
                    s = y(s, f, d, l, c[p + 9], 21, -343485551),
                    l = l + g >>> 0,
                    s = s + b >>> 0,
                    f = f + x >>> 0,
                    d = d + w >>> 0
                }
                return t.endian([l, s, f, d])
            }

t = {
    wordsToBytes: function(e) {
        for (var t = [], n = 0; n < 32 * e.length; n += 8)
            t.push(e[n >>> 5] >>> 24 - n % 32 & 255);
        return t
    },
    bytesToWords: function(e) {
        for (var t = [], n = 0, r = 0; n < e.length; n++,
        r += 8)
            t[r >>> 5] |= e[n] << 24 - r % 32;
        return t
    },
    bytesToHex: function(e) {
        for (var t = [], n = 0; n < e.length; n++)
            t.push((e[n] >>> 4).toString(16)),
            t.push((15 & e[n]).toString(16));
        return t.join("")
    },
    rotl: function(e, t) {
        return e << t | e >>> 32 - t
    },
    rotr: function(e, t) {
        return e << 32 - t | e >>> t
    },

    endian: function(e) {
        if (e.constructor == Number)
            return 16711935 & t.rotl(e, 8) | 4278255360 & t.rotl(e, 24);
        for (var x = 0; x < e.length; x++)
            e[x] = t.endian(e[x]);
        return e
    }
}

aaa = function (e,n){
    // if (void 0 === e || null === e)
    //     throw new Error("Illegal argument " + e);
    var r = t.wordsToBytes(i(e, n));
    return n && n.asBytes ? r : n && n.asString ? a.bytesToString(r) : t.bytesToHex(r)
}

var get_pas_word = function(e,a){
    var o = e.password
      , s = e.mobile
      // , a = ot()
    var l = D.ne("".concat(a).concat(o), aaa(("".concat(s.slice(3)).concat(a))));
    return l
}
var e_dic = {
    "mobile": "17836797789",
    "password": "123wu123123"
}
a = 1686207919
console.log(get_pas_word(e_dic,a))

最终python登录代码:

# -*- coding: UTF-8 -*-
'''
@Project :wbh_pj 
@File :111.py
@Author :hao
@Date :2023/6/8 15:11 
'''
import time
import execjs
import requests

headers = {
    'authority': 'api.fxbaogao.com',
    'accept': '*/*',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7',
    'content-type': 'application/json; charset=UTF-8',
    'origin': 'https://www.fxbaogao.com',
    'referer': 'https://www.fxbaogao.com/',
    'sec-ch-ua': '"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36',
    'user-id': '0',
    'user-token': 'QQ',
}

def get_pas_word(e_dic,now_time):
    filename_js = '123.js'
    with open(filename_js, encoding='utf-8', mode='r') as f:
        pw_js = f.read()
        f.close()
    js1 = execjs.compile(pw_js)
    print('********* 正在生成 -- pas_word *********')
    pas_word = js1.call('get_pas_word', e_dic,now_time)         # 获取token
    print(pas_word)
    return pas_word

def login():
    now_time = int(time.time())      # 获取时间戳
    e_dic = {
        "mobile": "17688888888",
        "password": "123wu123123"
    }
    pas_word = get_pas_word(e_dic,now_time)

    json_data = {
        'mobile': '17688688515',
        'data': pas_word,
        'time': now_time,
    }
    session = requests.session()
    # response = requests.post('https://api.fxbaogao.com/mofoun/user/login/byPhoneNumber', headers=headers,
    #                          json=json_data)
    response1 = session.post('https://api.fxbaogao.com/mofoun/user/login/byPhoneNumber', headers=headers,json=json_data)
    print(response1.text)

if __name__ == '__main__':
    login()

JS逆向部分总结:

        1、在登录的时候对password参数与时间戳拼接;

        2、对账号参数切及时间戳的拼接;

        3、将部分关键数据转数组后先进行MD5加密处理;

        4、最终将前面数据全部拼接对数据使用AES下的CBC加密

        5、得到password的参数之后使用request模块的Session进行登录操作

AES加密解密:AES加密和解密详解_aesdecrypt_浩·的博客-CSDN博客

知名外卖平台项目:JS逆向---获取某知名外卖平台数据(_token)_浩·的博客-CSDN博客

更多爬虫项目请看:浩 的个人爬虫项目

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

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

相关文章

证券公司软件测试面试总结分享!

这家公司是做证券项目的&#xff0c;约的9点钟&#xff0c;路程还是有点遥远&#xff0c;转了一趟公交两趟地铁&#xff0c;精力都花在了路上&#xff0c;感觉有点累&#xff0c;以下是今天得面试流程。 到公司前台给我了一张面试表&#xff0c;写完之后就是等待面试。一共面试…

Java开发手册中为什么禁止使用isSuccess作为布尔类型变量名以及POJO中基本类型与包装类型的使用标准

场景 Java开发手册中关于POJO的布尔类型的变量名的要求是: 【强制】POJO 类中的任何布尔类型的变量&#xff0c;都不要加 is 前缀&#xff0c;否则部分框架解析会引起序列化错误。 说明&#xff1a;在本文 MySQL 规约中的建表约定第一条&#xff0c;表达是与否的变量采用 is…

【新版】系统架构设计师 - 知识产权与标准化

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 知识产权与标准化考点摘要保护范围与对象识产权与保护期限知识产权与知识产权人的确定知识产权与侵权判定标准化&#xff08;标准分类与编号&#xff09; 架构 - 知识产权与标准化 考点摘要 保护…

JavaSE-06 【面向对象+封装】

JavaSE-06 [面向对象封装] 第一章 面向对象思想 1.1 面向过程和面向对象 面向过程&#xff1a; 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候一个一个依次调用就可以了面向对象&#xff1a; 面向对象是把构成…

windows服务器自带IIS搭建网站并发布公网访问的详细教程

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xff0c;比如…

关于数据生成二维码保存和解密删除二维码

文章目录 前言一、pom配置依赖二、文件引入1.BufferedImageLuminanceSource2.QRCodeUtil3.MyPicConfig4.UploadUtils三、测试前言 所需文件: MyPicConfig 主要解决上传图片实时刷新BufferedImageLuminanceSource 算法文件QRCodeUtil 生成二维码工具类UploadUtils 主要解决上传…

Python中模块的动态导入和自动安装

前言 在 Python 开发中&#xff0c;正确管理和安装所需的第三方模块是至关重要的&#xff0c;但手动处理模块依赖可能会变得繁琐且容易出错。 为了简化这一过程&#xff0c;Python 提供了动态导入和自动安装模块的能力。本文将介绍如何使用动态导入和自动安装模块的方法&#x…

自学黑客技术很难吗?如何自学黑客技术

有人的地方就有江湖&#xff0c;有互联网江湖的地方就有web安全工程师的身影。随着移动互联网的快速发展&#xff0c;网络安全问题成为越来越重要的事情&#xff0c;但由于之前国家对网络安全的不重视&#xff0c;导致网络安全人才严重缺失&#xff0c;所以成为一名网络安全工程…

爱眼护眼的倡导者,康瞳护眼吧引领更多的人关注眼部健康

爱眼护眼的倡导者,康瞳护眼吧引领更多的人关注眼部健康#微信热点#康瞳护眼膏百收网SEO 大家早上好有好消息告诉大家 人民日报连续❷大版面报道&#x1f4f0; 关于 ❝青少年近视眼防控的宣传❞ ——降 低近视率迫在眉睫‼️ 轻体营开营倒计时 ⏰⏰3天⏰⏰ 来此一生&#x…

微信小程序实现生成分享海报案例

一、引入插件painter &#xff08;1&#xff09;克隆地址&#xff1a;https://gitcode.net/mirrors/Kujiale-Mobile/Painter &#xff08;2&#xff09;下载的painter放到小程序的components目录下 二、页面中引入插件 &#xff08;1&#xff09;页面的json文件 "using…

最强总结,Python自动化测试-sign签名实战,精品整理...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 一般公司对外的接…

如何在一周内成为更好的软件测试员?七个步骤带你一步一步晋升...

一、引言 软件测试是确保软件质量的关键步骤&#xff0c;而成为一个更好的软件测试员并非一朝一夕之事。然而&#xff0c;通过对一些关键领域的集中学习和实践&#xff0c;我们可以在短短一周内取得显著的进步。下面&#xff0c;我们将为你提供一周内可以立即执行并取得结果的…

豆瓣评分9.2,最牛R语言实战书升级版来了!

R 作为一个开源项目&#xff0c;在很多操作系统上都可以免费获得&#xff0c;包括 Windows、macOS 和 Linux。不管你用 R 做数据收集、汇总、转换&#xff0c;还是探索、建模、可视化或展示方面的工作&#xff0c;它都可以满足你。 目前 R 已经成为统计、预测分析和数据可视化…

【Protobuf速成指南】.proto文件的编写与编译

文章目录 1.0版本一、编写.proto文件1.文件规范&#xff1a;2.注释方式&#xff1a;3.指定proto3语法&#xff1a;4.package申明符5.定义message6.编写消息字段①类型对照表②唯一编号 二、编译.proto文件1. 编译指令2.源码分析 三、序列化和反序列化的使用四、小结 1.0版本 本…

(基于python)简单实现接口自动化测试

一、简介 本文从一个简单的登录接口测试入手&#xff0c;一步步调整优化接口调用姿势&#xff0c;然后简单讨论了一下接口测试框架的要点&#xff0c;最后介绍了一下我们目前正在使用的接口测试框架pithy。期望读者可以通过本文对接口自动化测试有一个大致的了解。 二、引言 …

Volo.Abp升级小记(二)创建全新微服务模块

文章目录 创建模块领域层应用层数据库和仓储控制器配置微服务 测试微服务微服务注册添加资源配置配置网关 运行项目 假设有一个按照 官方sample搭建的微服务项目&#xff0c;并安装好了abp-cli。 需要创建一个名为GDMK.CAH.Common的模块&#xff0c;并在模块中创建标签管理功能…

Redis指令-数据结构String类型和Hash类型

1. String类型 字符串类型&#xff0c;Redis中最简单的存储类型 底层都是字节数组形式存储&#xff0c;只不过是编码方式不同&#xff1b; 字符串类型的最大空间不能超过512m&#xff1b; SET/GET/MSET/MGET使用示例&#xff1a; INCR使用示例&#xff1a; INCRBY自增并指定步长…

GIS在地质灾害危险性评估与灾后重建中的应用

地质灾害是指全球地壳自然地质演化过程中&#xff0c;由于地球内动力、外动力或者人为地质动力作用下导致的自然地质和人类的自然灾害突发事件。由于降水、地震等自然作用下&#xff0c;地质灾害在世界范围内频繁发生。我国除滑坡灾害外&#xff0c;还包括崩塌、泥石流、地面沉…

摩托车电动车头盔新标准GB811-2022?将于2023年7月1日强制实施!

头部在发生交通事故时受到猛烈撞击&#xff0c;头盔可以有效地保护头部。因为头盔光滑的半球性&#xff0c;可使冲击力分散并吸收冲击力&#xff0c;而头盔的变形或裂纹以及护垫&#xff0c;又起到一个缓冲作用&#xff0c;也能吸收一部分能量。据测算&#xff1a;人的头部一般…

Splashtop 荣获2023年 NAB 展会年度产品奖

2023年4月20日 加利福尼亚州库比蒂诺 Splashtop 在简便快捷的远程办公解决方案方案领域处于领先地位。Splashtop 宣布其 Enterprise 解决方案在2023年 NAB 展会年度产品奖项中荣获远程制作奖。NAB 展会的官方奖励计划旨在表彰参展商在今年的 NAB 展会上展出的一些重要的、有前…