某小程序sign签名参数逆向分析

文章目录

  • 1. 写在前面
  • 2. 接口分析
  • 3. 分析还原

【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!

1. 写在前面

  做爬虫脑子一定要灵光~不然怎么当大佬!正所谓大路不通走水路,水路不通走山路!相信很多工程师在面对一个端采集遇到瓶颈的时候什么M端、APP端、小程序端、Web端都几乎会挑一个或多个去摸排分析一下。风控这个东西很奇妙也很玄学,有时候还真有那么一些低风控的接口或者端存在

小程序端这么说吧,体量大点的风控基本都拉的很强且有的跟某信有因果!除非真的没有其他端的数据源了,一般少有人去(总之非可选的主流战场


分析目标

5Lmd5YS/572R57uc

2. 接口分析

随便通过关键词在搜索接口看看发包情况,直接Curl重放是403无效的,会出现签名错误。这个如下所示:

在这里插入图片描述

也就是说上面的sign是动态实时生成的,另外还有一个token参数也是有时效性的,将请求参数拿出来看看,如下所示:

data = {"method": "serverless.function.runtime.invoke","params": "{\"functionTarget\":\"DCloud-clientDB\",\"functionArgs\":{\"command\":{\"$db\":[{\"$method\":\"collection\",\"$param\":[\"a_novels\"]},{\"$method\":\"where\",\"$param\":[{\"novels_name\":{\"$regexp\":{\"source\":\"玄幻\",\"flags\":\"\"}}}]},{\"$method\":\"get\",\"$param\":[]}]},\"clientInfo\":{\"PLATFORM\":\"mp-weixin\",\"OS\":\"mac\",\"APPID\":\"__UNI__1B787A1\",\"DEVICEID\":\"17356293548006764541\",\"scene\":1089,\"deviceId\":\"17356293548006764541\",\"appId\":\"__UNI__1B787A1\",\"appName\":\"\",\"appVersion\":\"2.0.1\",\"appVersionCode\":\"201\",\"appLanguage\":\"zh-Hans\",\"uniCompilerVersion\":\"4.36\",\"uniRuntimeVersion\":\"4.36\",\"uniPlatform\":\"mp-weixin\",\"deviceBrand\":\"apple\",\"deviceModel\":\"MacBookPro15,2\",\"deviceType\":\"pc\",\"osName\":\"mac\",\"osVersion\":\"OS\",\"hostVersion\":\"3.8.7\",\"hostName\":\"WeChat\",\"locale\":\"zh-Hans\",\"LOCALE\":\"zh-Hans\"},\"uniIdToken\":\"\"}}","spaceId": "mp-f510ce55-6e44-40b0-b249-d278726b813d","timestamp": 1735785506387,"token": "63bc0ed6-f8a1-40ab-bb07-d11bf678ab21"
}

初看大概是MD5标准加密,拼接一下路径、参数加个时间戳啥的

3. 分析还原

在开始分析JS之前,可以通过反编译小程序的方式去分析也可以使用大佬开源的调试工具来辅助分析,有些Web端分析比较多的使用这个辅助还是蛮不错的。如下所示:

在这里插入图片描述

这里在页面直接搜索的话有时候资源多会比较慢,可以直接通过堆栈或者编译好的JS文件简单的进行静态分析,如下所示:

在这里插入图片描述

可以看到Ae就是生成签名的调用方法,而clientSecret是一段密文,后续它将参与加密用到,如下所示:

在这里插入图片描述

直接跳转到Ae方法处,可以看到Object.keys(e).sort()获取e对象里面的所有键进行一个排序,存在的话则使用&拼接,然后去掉所拼接字符串开头多余的一个&确保格式正确,如下所示:

在这里插入图片描述
在这里插入图片描述

接下来需要分析P方法,也就是最终的加密算法实现,进入当前方法直接来到了如下所示:

_createHmacHelper: function(e) {return function(t, n) {return new d.HMAC.init(e,n).finalize(t)}
}

这里推测d.HMAC是个库框架中的HMAC实现,而init(e, n)finalize(t)是该HMAC对象的初始化和最终处理方法,注意入口处的时候是使用了密钥的,所以要分析是什么算法难度是不大的,肯定不是一个单纯的MD5,对应JS代码如下所示:

var k = A((function(e, t) {var n;e.exports = n = n || function(e, t) {var n = Object.create || function() {function e() {}return function(t) {var n;return e.prototype = t,n = new e,e.prototype = null,n}}(), r = {}, o = r.lib = {}, i = o.Base = {extend: function(e) {var t = n(this);return e && t.mixIn(e),t.hasOwnProperty("init") && this.init !== t.init || (t.init = function() {t.$super.init.apply(this, arguments)}),t.init.prototype = t,t.$super = this,t},create: function() {var e = this.extend();return e.init.apply(e, arguments),e},init: function() {},mixIn: function(e) {for (var t in e)e.hasOwnProperty(t) && (this[t] = e[t]);e.hasOwnProperty("toString") && (this.toString = e.toString)},clone: function() {return this.init.prototype.extend(this)}}, a = o.WordArray = i.extend({init: function(e, t) {e = this.words = e || [],this.sigBytes = null != t ? t : 4 * e.length},toString: function(e) {return (e || c).stringify(this)},concat: function(e) {var t = this.words, n = e.words, r = this.sigBytes, o = e.sigBytes;if (this.clamp(),r % 4)for (var i = 0; i < o; i++) {var a = n[i >>> 2] >>> 24 - i % 4 * 8 & 255;t[r + i >>> 2] |= a << 24 - (r + i) % 4 * 8}elsefor (i = 0; i < o; i += 4)t[r + i >>> 2] = n[i >>> 2];return this.sigBytes += o,this},clamp: function() {var t = this.words, n = this.sigBytes;t[n >>> 2] &= 4294967295 << 32 - n % 4 * 8,t.length = e.ceil(n / 4)},clone: function() {var e = i.clone.call(this);return e.words = this.words.slice(0),e},random: function(t) {for (var n, r = [], o = function(t) {t = t;var n = 987654321, r = 4294967295;return function() {var o = ((n = 36969 * (65535 & n) + (n >> 16) & r) << 16) + (t = 18e3 * (65535 & t) + (t >> 16) & r) & r;return o /= 4294967296,(o += .5) * (e.random() > .5 ? 1 : -1)}}, i = 0; i < t; i += 4) {var u = o(4294967296 * (n || e.random()));n = 987654071 * u(),r.push(4294967296 * u() | 0)}return new a.init(r,t)}}), u = r.enc = {}, c = u.Hex = {stringify: function(e) {for (var t = e.words, n = e.sigBytes, r = [], o = 0; o < n; o++) {var i = t[o >>> 2] >>> 24 - o % 4 * 8 & 255;r.push((i >>> 4).toString(16)),r.push((15 & i).toString(16))}return r.join("")},parse: function(e) {for (var t = e.length, n = [], r = 0; r < t; r += 2)n[r >>> 3] |= parseInt(e.substr(r, 2), 16) << 24 - r % 8 * 4;return new a.init(n,t / 2)}}, s = u.Latin1 = {stringify: function(e) {for (var t = e.words, n = e.sigBytes, r = [], o = 0; o < n; o++) {var i = t[o >>> 2] >>> 24 - o % 4 * 8 & 255;r.push(String.fromCharCode(i))}return r.join("")},parse: function(e) {for (var t = e.length, n = [], r = 0; r < t; r++)n[r >>> 2] |= (255 & e.charCodeAt(r)) << 24 - r % 4 * 8;return new a.init(n,t)}}, l = u.Utf8 = {stringify: function(e) {try {return decodeURIComponent(escape(s.stringify(e)))} catch (e) {throw new Error("Malformed UTF-8 data")}},parse: function(e) {return s.parse(unescape(encodeURIComponent(e)))}}, f = o.BufferedBlockAlgorithm = i.extend({reset: function() {this._data = new a.init,this._nDataBytes = 0},_append: function(e) {"string" == typeof e && (e = l.parse(e)),this._data.concat(e),this._nDataBytes += e.sigBytes},_process: function(t) {var n = this._data, r = n.words, o = n.sigBytes, i = this.blockSize, u = o / (4 * i), c = (u = t ? e.ceil(u) : e.max((0 | u) - this._minBufferSize, 0)) * i, s = e.min(4 * c, o);if (c) {for (var l = 0; l < c; l += i)this._doProcessBlock(r, l);var f = r.splice(0, c);n.sigBytes -= s}return new a.init(f,s)},clone: function() {var e = i.clone.call(this);return e._data = this._data.clone(),e},_minBufferSize: 0});o.Hasher = f.extend({cfg: i.extend(),init: function(e) {this.cfg = this.cfg.extend(e),this.reset()},reset: function() {f.reset.call(this),this._doReset()},update: function(e) {return this._append(e),this._process(),this},finalize: function(e) {return e && this._append(e),this._doFinalize()},blockSize: 16,_createHelper: function(e) {return function(t, n) {return new e.init(n).finalize(t)}},_createHmacHelper: function(e) {return function(t, n) {return new d.HMAC.init(e,n).finalize(t)}}});var d = r.algo = {};return r}(Math)
}
)), P = (A((function(e, t) {var n;e.exports = (n = k,function(e) {var t = n, r = t.lib, o = r.WordArray, i = r.Hasher, a = t.algo, u = [];!function() {for (var t = 0; t < 64; t++)u[t] = 4294967296 * e.abs(e.sin(t + 1)) | 0}();var c = a.MD5 = i.extend({_doReset: function() {this._hash = new o.init([1732584193, 4023233417, 2562383102, 271733878])},_doProcessBlock: function(e, t) {for (var n = 0; n < 16; n++) {var r = t + n, o = e[r];e[r] = 16711935 & (o << 8 | o >>> 24) | 4278255360 & (o << 24 | o >>> 8)}var i = this._hash.words, a = e[t + 0], c = e[t + 1], p = e[t + 2], h = e[t + 3], g = e[t + 4], v = e[t + 5], y = e[t + 6], m = e[t + 7], b = e[t + 8], w = e[t + 9], _ = e[t + 10], x = e[t + 11], S = e[t + 12], O = e[t + 13], A = e[t + 14], k = e[t + 15], P = i[0], T = i[1], j = i[2], E = i[3];P = s(P, T, j, E, a, 7, u[0]),E = s(E, P, T, j, c, 12, u[1]),j = s(j, E, P, T, p, 17, u[2]),T = s(T, j, E, P, h, 22, u[3]),P = s(P, T, j, E, g, 7, u[4]),E = s(E, P, T, j, v, 12, u[5]),j = s(j, E, P, T, y, 17, u[6]),T = s(T, j, E, P, m, 22, u[7]),P = s(P, T, j, E, b, 7, u[8]),E = s(E, P, T, j, w, 12, u[9]),j = s(j, E, P, T, _, 17, u[10]),T = s(T, j, E, P, x, 22, u[11]),P = s(P, T, j, E, S, 7, u[12]),E = s(E, P, T, j, O, 12, u[13]),j = s(j, E, P, T, A, 17, u[14]),P = l(P, T = s(T, j, E, P, k, 22, u[15]), j, E, c, 5, u[16]),E = l(E, P, T, j, y, 9, u[17]),j = l(j, E, P, T, x, 14, u[18]),T = l(T, j, E, P, a, 20, u[19]),P = l(P, T, j, E, v, 5, u[20]),E = l(E, P, T, j, _, 9, u[21]),j = l(j, E, P, T, k, 14, u[22]),T = l(T, j, E, P, g, 20, u[23]),P = l(P, T, j, E, w, 5, u[24]),E = l(E, P, T, j, A, 9, u[25]),j = l(j, E, P, T, h, 14, u[26]),T = l(T, j, E, P, b, 20, u[27]),P = l(P, T, j, E, O, 5, u[28]),E = l(E, P, T, j, p, 9, u[29]),j = l(j, E, P, T, m, 14, u[30]),P = f(P, T = l(T, j, E, P, S, 20, u[31]), j, E, v, 4, u[32]),E = f(E, P, T, j, b, 11, u[33]),j = f(j, E, P, T, x, 16, u[34]),T = f(T, j, E, P, A, 23, u[35]),P = f(P, T, j, E, c, 4, u[36]),E = f(E, P, T, j, g, 11, u[37]),j = f(j, E, P, T, m, 16, u[38]),T = f(T, j, E, P, _, 23, u[39]),P = f(P, T, j, E, O, 4, u[40]),E = f(E, P, T, j, a, 11, u[41]),j = f(j, E, P, T, h, 16, u[42]),T = f(T, j, E, P, y, 23, u[43]),P = f(P, T, j, E, w, 4, u[44]),E = f(E, P, T, j, S, 11, u[45]),j = f(j, E, P, T, k, 16, u[46]),P = d(P, T = f(T, j, E, P, p, 23, u[47]), j, E, a, 6, u[48]),E = d(E, P, T, j, m, 10, u[49]),j = d(j, E, P, T, A, 15, u[50]),T = d(T, j, E, P, v, 21, u[51]),P = d(P, T, j, E, S, 6, u[52]),E = d(E, P, T, j, h, 10, u[53]),j = d(j, E, P, T, _, 15, u[54]),T = d(T, j, E, P, c, 21, u[55]),P = d(P, T, j, E, b, 6, u[56]),E = d(E, P, T, j, k, 10, u[57]),j = d(j, E, P, T, y, 15, u[58]),T = d(T, j, E, P, O, 21, u[59]),P = d(P, T, j, E, g, 6, u[60]),E = d(E, P, T, j, x, 10, u[61]),j = d(j, E, P, T, p, 15, u[62]),T = d(T, j, E, P, w, 21, u[63]),i[0] = i[0] + P | 0,i[1] = i[1] + T | 0,i[2] = i[2] + j | 0,i[3] = i[3] + E | 0},_doFinalize: function() {var t = this._data, n = t.words, r = 8 * this._nDataBytes, o = 8 * t.sigBytes;n[o >>> 5] |= 128 << 24 - o % 32;var i = e.floor(r / 4294967296), a = r;n[15 + (o + 64 >>> 9 << 4)] = 16711935 & (i << 8 | i >>> 24) | 4278255360 & (i << 24 | i >>> 8),n[14 + (o + 64 >>> 9 << 4)] = 16711935 & (a << 8 | a >>> 24) | 4278255360 & (a << 24 | a >>> 8),t.sigBytes = 4 * (n.length + 1),this._process();for (var u = this._hash, c = u.words, s = 0; s < 4; s++) {var l = c[s];c[s] = 16711935 & (l << 8 | l >>> 24) | 4278255360 & (l << 24 | l >>> 8)}return u},clone: function() {var e = i.clone.call(this);return e._hash = this._hash.clone(),e}});function s(e, t, n, r, o, i, a) {var u = e + (t & n | ~t & r) + o + a;return (u << i | u >>> 32 - i) + t}function l(e, t, n, r, o, i, a) {var u = e + (t & r | n & ~r) + o + a;return (u << i | u >>> 32 - i) + t}function f(e, t, n, r, o, i, a) {var u = e + (t ^ n ^ r) + o + a;return (u << i | u >>> 32 - i) + t}function d(e, t, n, r, o, i, a) {var u = e + (n ^ (t | ~r)) + o + a;return (u << i | u >>> 32 - i) + t}t.MD5 = i._createHelper(c),t.HmacMD5 = i._createHmacHelper(c)}(Math),n.MD5)
}
)),
A((function(e, t) {var n;e.exports = (n = k,void function() {var e = n, t = e.lib.Base, r = e.enc.Utf8;e.algo.HMAC = t.extend({init: function(e, t) {e = this._hasher = new e.init,"string" == typeof t && (t = r.parse(t));var n = e.blockSize, o = 4 * n;t.sigBytes > o && (t = e.finalize(t)),t.clamp();for (var i = this._oKey = t.clone(), a = this._iKey = t.clone(), u = i.words, c = a.words, s = 0; s < n; s++)u[s] ^= 1549556828,c[s] ^= 909522486;i.sigBytes = a.sigBytes = o,this.reset()},reset: function() {var e = this._hasher;e.reset(),e.update(this._iKey)},update: function(e) {return this._hasher.update(e),this},finalize: function(e) {var t = this._hasher, n = t.finalize(e);return t.reset(),t.finalize(this._oKey.clone().concat(n))}})}())
}
)),
A((function(e, t) {e.exports = k.HmacMD5
}

通过对上面的JS代码进行静态分析可发现最终使用的加密为HmacMD5,结合有密钥参与加密,可以直接Python进行算法还原测试验证即可,算法实现如下所示:

def sign(e, t):sorted_keys = sorted(e.keys())n = ""for key in sorted_keys:if e[key]:n += f"&{key}={e[key]}"n = n[1:]signature = hmac.new(t.encode('utf-8'), n.encode('utf-8'), hashlib.md5).hexdigest()return signature

HMAC-MD5是基于MD5哈希算法的消息认证码,HMAC是一种利用密钥的哈希算法,用于生成一个“签名”,并且常用于消息的完整性验证和认证(如API请求签名)

最终简单的编写一个搜索代码查看一下是否可以正常获取到相关的数据,如下所示:

在这里插入图片描述

案列比较简单,适合正在或想要学习逆向新手小伙伴练练手

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2000-2020年各省财政一般预算支出面板数据

2000-2020年各省财政一般预算支出面板数据 1、时间&#xff1a;2000-2020年 2、来源&#xff1a;国家统计局 3、指标&#xff1a;年份、省份、地方财政一般预算支出 4、范围&#xff1a;31省 指标解释&#xff1a;地方财政一般预算支出‌是指地方ZF根据预算安排&#xff0…

[羊城杯 2024]1z_misc

得到FL4G.zip和天机不可泄露.txt文件&#xff0c;其中压缩包需要解压密码&#xff1a; 二十八星宿&#xff1a; 东方苍龙七宿&#xff1a;角、亢、氐、房、心、尾、箕 南方朱雀七宿&#xff1a;鬼、井、柳、星、张、翼、轸 西方白虎七宿&#xff1a;奎、娄、胃、昴、毕、觜、…

右值引用全面剖析

为什么要有右值引用&#xff0c;右值引用出现前程序员们的困境&#xff1a; 在右值引用出现以前&#xff0c;想要把一块内存空间里的内容放到另一块内存空间&#xff0c;只能再开辟一块内存&#xff0c;然后将原来内存里的内容复制到新开辟的内存里&#xff0c;然后再把原来的…

mac下载Homebrew安装nvm

通过Homebrew安装 - 国内下载地址 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"安装nvm brew install nvm 配置nvm环境变量 export NVM_DIR“$HOME/.nvm” [ -s “/usr/local/opt/nvm/nvm.sh” ] && . “/usr/…

解决chatgpt(mac app登陆)出现报错:获取您的 SSO 信息时出错

由于我们是app登陆的&#xff0c;不能直接修改网站的链接&#xff0c;将 URL 的域名部分从 auth.openai.com 变更为 auth0.openai.com&#xff0c;然后加载新的地址&#xff0c;这时候应该就可以正常登录或注册了。 所以我们使用邮箱先载入auth0的地址&#xff0c;再更改自己的…

C#编写的盘符图标修改器 - 开源研究系列文章

这天在网上遇到一个Windows的盘符图标修改软件&#xff0c;但是它那个是.net framework 2.0的&#xff0c;所以就将其改成4.8.1的了&#xff0c;用于Windows 11等默认不安装2.0库的操作系统里使用。 1、 项目目录&#xff1b; 2、 源码介绍&#xff1b; 它直接进行注册表的修改…

【第二部分--Python之基础】03 容器类型的数据

Python内置的数据类型如序列&#xff08;列表、元组等&#xff09;、集合和字典等可以容纳多项数据&#xff0c;我们称它们为容器类型的数据。 序列 序列&#xff08;sequence&#xff09;是一种可迭代的、元素有序的容器类型的数据。 序列包括列表&#xff08;list&#xff…

HTML5实现好看的二十四节气网页源码

HTML5实现好看的新年春节元旦网站源码 前言一、设计来源1.1 主界面1.2 关于我们界面1.3 春季节气界面1.4 夏季节气界面1.5 秋季节气界面1.6 冬季节气界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看的二十四节气网页源码&#xff0c;春季节气&#xf…

走进深圳华为总部参观研学

在这个科技日新月异的时代&#xff0c;每一次与行业标杆企业领先者对话&#xff0c;都是开眼界的好时机。华研标杆游学高老师组织了一场企业家参访团体考察&#xff0c;带大家去到深圳华为总部研学&#xff0c;亲身感受科技巨头的风采&#xff0c;一起探讨未来的发展。 第一站-…

【unity错误】Unity 6 LTS 打开就报错Assertion failed on expressionxxx?

unity6发布已经有一段时间了&#xff0c;如果目前你已经使用了unity6进行项目开发&#xff0c;可能打开会发现如下报错 Assertion failed on expression: ‘!(o->TestHideFlag(Object::kDontSaveInEditor) && (options & kAllowDontSaveObjectsToBePersistent) …

集线器,交换机,路由器,mac地址和ip地址知识记录总结

一篇很不错的视频简介 基本功能 从使用方面来说&#xff0c;都是为了网络传输的标识&#xff0c;和机器确定访问对象 集线器、交换机和路由器 常听到路由器和集线器&#xff0c;下面是区别&#xff1a; 集线器 集线器&#xff1a;一个简单的物理扩展接口数量的物理硬件。…

【ArcGISPro/GeoScenePro】检查并处理高程数据

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 数字高程模型 (DEM) 是一种栅格,可显示地面或地形的高程。 数字表面模型 (DSM) 是另一种高程栅格,可显示表面的高度,例如建筑物或树冠的顶部。 您需要准备 DEM 和 DSM 以供分析…

《计算机组成及汇编语言原理》阅读笔记:p177-p177

《计算机组成及汇编语言原理》学习第 13 天&#xff0c;p177-p177 总结&#xff0c;总计 1 页。 一、技术总结 1.real mode A programming model where the program has access to the entire capability of the machine, bypassing security and memory management. Useful…

珞珈一号夜光遥感数据地理配准,栅格数据地理配准

目录 一、夜光数据下载&#xff1a; 二、夜光遥感数据地理配准 三、计算夜光数据值 四、辐射定标 五、以表格显示分区统计 五、结果验证 夜光数据位置和路网位置不匹配&#xff0c;虽然都是WGS84坐标系&#xff0c;不匹配&#xff01;&#xff01;&#xff01;不要看到就直接…

python学习笔记—12—

1. 布尔类型 (1) 定义 (2) 比较运算符 (3) 代码演示 1. 手动定义 bool_1 True bool_2 False print(f"bool_1的内容是&#xff1a;{bool_1}, 类型是&#xff1a;{type(bool_1)}") print(f"bool_2的内容是&#xff1a;{bool_2}, 类型是&#xff1a;{type(bool…

Redis 使用redisTemplate获取某个规则下的key的全量数据(示例Set结构)

如下是redis中存储的数据结构 我想取key以favorites:结尾的所有数据 Redis 的 SCAN 命令用于迭代数据库中的键&#xff0c;支持通过模式过滤结果。模式规则基于 Redis 的通配符匹配语法&#xff0c;类似于文件名匹配规则&#xff1a; *&#xff1a;匹配零个或多个字符。?&…

CertiK《Hack3d:2024年度安全报告》(附报告全文链接)

CertiK《Hack3d&#xff1a;2024年度安全报告》现已发布&#xff0c;本次报告深入分析了2024年Web3.0领域的安全状况。2024年损失总额超过23亿美元&#xff0c;同比增幅高达31.61%&#xff1b;其中&#xff0c;12月的损失金额最少。过去一年&#xff0c;网络钓鱼攻击和私钥泄露…

AI知识库与用户行为分析:优化用户体验的深度洞察

在当今数字化时代&#xff0c;用户体验&#xff08;UX&#xff09;已成为衡量产品成功与否的关键指标之一。AI知识库作为智能客服系统的重要组成部分&#xff0c;不仅为用户提供快速、准确的信息检索服务&#xff0c;还通过用户行为分析&#xff0c;为产品优化提供了深度洞察。…

Vue3 + ElementPlus动态合并数据相同的单元格(超级详细版)

最近的新项目有个需求需要合并单元列表。ElementPlus 的 Table 提供了合并行或列的方法&#xff0c;可以参考一下https://element-plus.org/zh-CN/component/table.html 但项目中&#xff0c;后台数据返回格式和指定合并是动态且没有规律的&#xff0c;Element 的示例过于简单&…

CSS进阶和SASS

目录 一、CSS进阶 1.1、CSS变量 1.2、CSS属性值的计算过程 1.3、做杯咖啡 1.4、下划线动画 1.5、CSS中的混合模式(Blending) 二、SASS 2.1、Sass的颜色函数 2.2、Sass的扩展(extend)和占位符(%)、混合(Mixin) 2.3、Sass的数学函数 2.4、Sass的模块化开发 2.5、Sass…