PHP数组去重排序
PHP数组去重排序
引言
数组是PHP最常用的数据结构,去重和排序是开发中反复出现的基础需求。掌握内置函数的正确用法,比自己写循环更高效,也更不容易出错。
一维数组去重
$arr = [3, 1, 2, 3, 1, 5];
$unique = array_unique($arr);
//结果保留首次出现的值,键名不变
注意array_unique默认按字符串比较,去重后键名可能不连续,需要重新索引:
$unique = array_values(array_unique($arr));
关联数组按字段去重
$users = [
['id'=>1,'name'=>'张三'],
['id'=>2,'name'=>'李四'],
['id'=>1,'name'=>'张三副本'],
];
$seen = [];
$unique = array_filter($users, function($item) use (&$seen) {
if (in_array($item['id'], $seen)) return false;
$seen[] = $item['id'];
return true;
});
排序函数区分
- sort():值升序,重置键名;
- rsort():值降序;
- asort():值升序,保留键名;
- ksort():按键名升序;
- krsort():按键名降序。
$score = ['张三'=>88,'王五'=>95,'李四'=>76];
arsort($score); //按分数降序,保留姓名键名
print_r($score);
自定义排序
$users = [['age'=>20],['age'=>35],['age'=>28]];
usort($users, function($a, $b) {
return $a['age'] <=> $b['age']; //太空船运算符比较
});
注意事项
- array_unique对多维数组不能直接按字段去重;
- sort系列会改变原数组,返回bool;
- 中文排序建议用Collator或mb方式;
- 去重后及时array_values重置索引,避免后续遍历异常。
总结
一维去重用array_unique加array_values,多维按字段用array_filter配合标记数组。排序先想清是否保留键名,普通值排序用sort/rsort,关联数组用asort/ksort,复杂规则用usort自定义比较。
Go语言channel缓冲与阻塞原理详解
前言
channel 是 Go 语言 goroutine 之间通信的核心机制,很多新手写并发程序时会遇到 goroutine 阻塞、死锁、数据写不进去等问题,根源都是没搞懂无缓冲通道和有缓冲通道的阻塞规则。本篇结合代码讲清 channel 的缓冲模型与阻塞时机。
一、无缓冲通道(unbuffered channel)
ch := make(chan int)
go func() {
ch <- 100 // 发送
}()
v := <-ch // 接收
无缓冲通道容量为 0,发送方必须等到有接收方准备好才会完成发送,属于"同步握手"。如果只发不收,发送 goroutine 会永久阻塞。
二、有缓冲通道(buffered channel)
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// 再发第4个会阻塞,直到有人接收腾出位置
容量为 3 时,前 3 个元素先进缓冲区,发送方不会阻塞;缓冲区满了之后,发送方才会等待。接收方在缓冲区为空时阻塞。
三、阻塞与关闭的常见规则
- 向已关闭的 channel 发送数据会 panic;
- 从已关闭的 channel 接收会得到零值,ok 标识为 false;
- 关闭后再次关闭会 panic;
- 遍历 channel 用 range,channel 关闭后 range 自动退出。
四、常见踩坑
- 忘记接收导致发送 goroutine 泄漏,程序卡住;
- 缓冲容量设置过大失去解耦意义,过小又频繁阻塞;
- 主 goroutine 提前退出,子协程数据没发完就被回收。
总结
无缓冲通道做同步通信,有缓冲通道做异步削峰。理解"缓冲区满则发送阻塞、空则接收阻塞"这两条规则,就能避开大部分 channel 死锁问题。
遇到问题加QQ23979811 协助处理
Git撤销提交操作
Git撤销提交操作
引言
提交错了、提交了不该上传的文件、commit message写错,都是日常开发常见情况。Git提供多种撤销方式,关键是分清:还没push的本地提交和已经推到远程的提交,处理方式完全不同。
场景一:改错还没add
工作区改动想放弃:
git checkout -- 文件名 #旧写法
git restore 文件名 #新写法
整个工作区全部还原:git restore .
场景二:add了但没commit
把文件撤出暂存区,保留改动:
git reset HEAD 文件名
git restore --staged 文件名
场景三:commit了但没push
改最后一次提交信息:
git commit --amend -m "新的提交说明"
撤销最近一次提交,改动留在工作区:
git reset --soft HEAD^ #撤销提交,代码保留在暂存区
git reset --mixed HEAD^ #默认,撤销提交,代码退回工作区
git reset --hard HEAD^ #撤销提交并丢弃改动,慎用
场景四:已经push到远程
本地reset后强推会改写历史,团队共用分支风险大,推荐用反向提交:
git revert 提交ID
revert会生成一个新提交,把那次改动反向抵消,不改写历史,安全协作。
注意事项
- --hard会丢代码,操作前先备份或开分支;
- 已push到共享分支不要随意reset强推;
- amend只用于未推送的本地提交;
- 查看历史用git log --oneline定位提交ID。
总结
未add用restore,add未commit用reset HEAD撤出,本地commit用reset(soft/mixed保留代码,hard丢弃),已push用revert新增反向提交。记住本地和远程的区别,避免改写团队历史。
MySQL事务ACID原理
MySQL事务ACID原理
引言
转账、下单、扣库存这类多步操作必须保证要么全成功要么全回滚,靠的就是数据库事务。ACID是事务四个特性的缩写,理解它才能用对事务、排查并发问题。
四个特性
A 原子性 Atomicity
事务内操作不可分割,全部成功提交,失败则整体回滚到事务前状态。InnoDB靠undo log回滚日志实现,记录修改前旧值,出错反向撤销。
C 一致性 Consistency
事务前后数据库从一个合法状态转到另一个合法状态,约束(主键、外键、唯一、余额非负)始终不被破坏。一致性是最终目标,靠ABD共同保障。
I 隔离性 Isolation
并发事务之间互不干扰,隔离级别从低到高:读未提交、读提交、可重复读(InnoDB默认)、串行化,用锁和MVCC实现。
D 持久性 Durability
事务一旦提交,修改永久落盘,宕机也不丢。靠redo log重做日志,先写日志再刷盘,崩溃后重放恢复。
事务用法示例
START TRANSACTION;
UPDATE account SET money = money - 100 WHERE id = 1;
UPDATE account SET money = money + 100 WHERE id = 2;
COMMIT;
-- 出错则 ROLLBACK;
PHP中配合PDO:
$pdo->beginTransaction();
try {
$pdo->exec("UPDATE account SET money=money-100 WHERE id=1");
$pdo->exec("UPDATE account SET money=money+100 WHERE id=2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
注意事项
- 事务不要包太长,避免长时间持锁;
- 只对必须一致的操作开事务;
- 一致性靠应用校验和数据库约束共同保证;
- 隔离级别越高并发越低,按业务权衡。
总结
ACID:原子性靠undo log、持久性靠redo log、隔离性靠锁与MVCC、一致性是目标。转账扣库存等关键链路用事务包裹,注意控制事务长度和隔离级别。
Nginx开启Gzip压缩
Nginx开启Gzip压缩
引言
HTML、CSS、JS、JSON这类文本资源直接传输体积大,首屏慢。Nginx开启Gzip后,服务端先压缩再发送,浏览器解压,通常文本体积能减少60%以上,是零成本提升访问速度的手段。
配置示例
在http块或server块加入:
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
参数说明
- gzip on:开关;
- gzip_min_length:小于1k不压缩,避免小文件压缩得不偿失;
- gzip_comp_level:压缩等级1-9,6比较均衡,太高CPU占用大;
- gzip_types:指定压缩的MIME类型,text/html默认自带;
- gzip_vary on:响应加Vary:Accept-Encoding,避免缓存问题;
- gzip_disable:兼容老旧IE浏览器。
生效验证
改完执行nginx -t检查语法,再reload。浏览器F12网络面板看响应头Content-Encoding:gzip即生效。
注意事项
- 图片、视频、PDF本身已压缩,不要再gzip,浪费CPU;
- 压缩等级不要盲目拉满,大文件压缩吃CPU;
- 静态资源可在CDN层提前压缩;
- 配合Brotli现代压缩效果更好,需Nginx额外模块支持。
总结
Gzip是文本类资源提速标配,开启后配置类型、大小阈值、合理压缩等级,reload生效,用响应头验证。不压缩图片视频,平衡传输体积与服务器CPU。
Docker容器日志查看
Docker容器日志查看
引言
容器启动报错、服务运行异常时,不能进容器盲目找文件,第一时间用docker logs查看标准输出日志。应用把日志打到stdout/stderr,Docker统一收集,是容器排错最常用入口。
基础查看命令
#查看全部日志
docker logs 容器名或ID
#实时跟踪最新日志,类似tail -f
docker logs -f 容器名
#只看最近100行
docker logs --tail 100 容器名
#查看某时间之后日志
docker logs --since 30m 容器名
docker logs --since 2026-10-18T07:00:00 容器名
#带时间戳
docker logs -t 容器名
常用组合
#实时跟踪并显示时间戳、最近50行
docker logs -f -t --tail 50 myapp
排查重启循环、接口报错时,先tail看最后报错,再结合时间过滤。
日志原理
Docker默认把容器标准输出保存到本地json-file驱动,存在宿主机/var/lib/docker/containers目录。应用要把日志输出到控制台,而不是只写容器内文件,才能被docker logs抓到。
日志注意事项
- 默认json-file不限制大小,长时间运行会占满磁盘,生产配置日志轮转:
{"log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"}} - 需要长期分析的日志,接入ELK等集中日志系统;
- 容器删除后默认日志随之丢失;
- 应用内部业务日志建议挂载到宿主机目录持久化。
总结
docker logs是容器排错第一命令,-f跟踪、--tail取尾部、--since按时间过滤。生产配置大小轮转避免磁盘写满,业务重要日志持久化或集中采集。
JS跨域CORS方案
JS跨域CORS方案
引言
浏览器同源策略要求协议、域名、端口完全一致,前端页面访问另一个域名的接口就会被拦截报跨域错误。CORS是目前最标准、主流的跨域解决方式,由后端配合响应头放行。
什么是同源策略
三个任意一项不同都算跨域:
- 协议不同(http/https);
- 域名不同;
- 端口不同。
CORS后端响应头(PHP)
<?php
header('Access-Control-Allow-Origin: https://www.example.com');
header('Access-Control-Allow-Methods: GET,POST,OPTIONS');
header('Access-Control-Allow-Headers: Content-Type,Authorization');
header('Access-Control-Allow-Credentials: true');
携带cookie时Allow-Origin不能写*,必须指定具体域名,且前端请求withCredentials为true。
预检请求OPTIONS
POST JSON等复杂请求,浏览器会先发OPTIONS预检,后端要直接放行OPTIONS,返回204,否则正式请求发不出去。
其他常见跨域方式对比
- JSONP:只支持GET,老旧方案;
- Nginx反向代理:前端请求同源nginx,由nginx转发接口,无浏览器跨域;
- 开发环境代理:Vite、Webpack devServer proxy临时解决。
注意事项
- 生产不要用*放开所有来源;
- 区分简单请求和预检请求;
- 配合cookie要带Credentials并指定来源;
- 接口服务端做好来源校验。
总结
CORS是前后端分离首选方案,核心是后端设置允许来源、方法、响应头。复杂请求先处理OPTIONS预检,带cookie时指定具体域名,安全可控,比JSONP和代理更规范。
PHP错误日志排查
PHP错误日志排查
引言
线上页面白屏、500错误,前台看不到具体原因,靠猜很难定位。正确做法是开启并查看PHP错误日志,从日志里拿到文件、行号、错误类型,再针对性修复。
先确认日志位置
在phpinfo页面搜索error_log,或查看php.ini配置:
#查看php.ini错误日志配置
php -i | grep error_log
#常见位置
tail -f /var/log/php-fpm/www-error.log
tail -f /var/log/httpd/php_errors.log
宝塔、面板环境一般在日志菜单里可直接打开PHP错误日志。
开发环境临时显示错误
<?php
ini_set('display_errors',1);
ini_set('error_reporting',E_ALL);
error_reporting(E_ALL);
生产环境不要直接display_errors到页面,统一写日志。
日志格式解读
一条错误日志通常包含:错误级别、报错文件、行号、错误信息。
[12-Oct-2026 07:00:00] PHP Fatal error:
Call to undefined function xxx() in /www/app/index.php on line 18
含义:第18行调用了不存在的函数,检查函数是否引入、命名是否拼错。
常见错误类型
- Fatal error:致命错误,程序中断,如调用不存在的类、函数;
- Warning:警告不中断,如参数类型不对;
- Notice:提示,未定义变量;
- Deprecated:废弃语法,未来版本不兼容。
排查流程
- 复现问题,记录访问时间;
- 按时间过滤日志,定位对应报错;
- 打开报错文件对应行号;
- 修复后清空日志再验证。
总结
PHP排错第一步永远是看错误日志,而不是反复刷新页面。开发环境显示错误,生产环境写日志,按时间和行号快速定位,养成记录日志的习惯。
Linux防火墙配置
Linux防火墙配置
引言
云服务器安全不能只靠系统防火墙,开通端口是上线必经步骤。CentOS 7以上默认使用firewalld,很多新手忘了放行端口,导致网站、SSH、数据库从外部连不上。
firewalld常用命令
#查看防火墙状态
firewall-cmd --state
#查看已放行端口
firewall-cmd --list-ports
#永久放行80、443端口
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
#重新加载生效
firewall-cmd --reload
#移除某个端口
firewall-cmd --permanent --remove-port=3306/tcp
区域概念
firewalld分public、internal等zone,默认public。--permanent写入永久规则,不加只对当前会话生效,重启失效。改完必须reload。
常用放行场景
- Web服务:80、443;
- SSH远程:22(建议改非默认端口再放行);
- 不要对公网开放3306、6379、27017等数据库端口;
- 只允许指定IP访问管理端口更安全:
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=1.2.3.4 port port=22 protocol=tcp accept'
注意事项
- 云服务器除了系统防火墙,还要在云厂商控制台安全组同步放行;
- 改SSH端口前先确认新端口放行,避免把自己挡在门外;
- 防火墙规则最小化,只开放必要端口。
总结
firewalld核心:查看列表、永久加端口、reload生效。记住系统防火墙和云安全组两层都要放行,数据库端口绝不暴露公网,管理端口限制来源IP。
Composer国内镜像配置
Composer国内镜像配置
引言
PHP项目用Composer安装依赖时,默认从packagist.org国外源拉取,国内网络经常超时、下载极慢。配置国内镜像源后,安装速度能提升数倍,是PHP开发环境必备操作。
全局配置方法
终端执行下面命令,把全局镜像换成国内源:
composer config -g repos.packagist composer https://mirrors.aliyun.com/composer/
执行后写入用户全局配置,所有项目生效。
单个项目配置
只给当前项目换源,在项目目录执行:
composer config repositories.packagist composer https://mirrors.aliyun.com/composer/
常见国内镜像
- 阿里云镜像:https://mirrors.aliyun.com/composer/
- 腾讯云镜像:https://mirrors.cloud.tencent.com/composer/
- 华为云镜像:https://repo.huaweicloud.com/repository/php/
验证与切换
#查看当前源
composer config -g repos
#回到官方源
composer config -g --unset repos.packagist
注意事项
- 镜像只加速包下载,不影响代码本身;
- 生产环境尽量锁定composer.lock版本;
- 个别新包国内镜像同步延迟,临时可切官方源;
- 配置后先清缓存再安装:
composer clearcache。
总结
Composer换源是国内PHP开发第一步,全局配置阿里云镜像即可解决大部分超时问题。安装依赖前先清缓存,项目锁定lock保证团队版本一致。
MySQL分页查询优化
MySQL分页查询优化
引言
后台列表、文章分页是最常见的SQL场景。数据量小的时候LIMIT偏移量查询很快,数据到几十万、上百万行,深分页会明显变慢,甚至拖垮数据库。
为什么深分页慢
LIMIT 100000,20 会先扫描前100000行再丢弃,只返回20行,扫描行数越多IO开销越大。
优化方案一:子查询定位主键
先查出起始主键,再用主键关联取数据,避免回表扫描:
SELECT a.* FROM article a
INNER JOIN (
SELECT id FROM article ORDER BY id LIMIT 100000,20
) b ON a.id=b.id;
子查询走覆盖索引,速度明显提升。
优化方案二:游标/记住上一页最大ID
SELECT * FROM article
WHERE id > 100000
ORDER BY id LIMIT 20;
每次只查比上一页最后一条ID大的记录,不走大偏移,适合无限滚动。
其他优化点
- 分页排序字段必须建索引,避免filesort;
- 只查询需要的字段,不用SELECT *;
- 禁止前端跳转到任意深页,后台限制最大页码;
- 超大表考虑分表、搜索引擎承接列表。
总结
深分页慢的根源是大偏移扫描。优先用主键游标翻页,配合覆盖索引;传统offset分页用子查询优化。列表数据量极大时,交给ES等搜索方案更合理。
PHP文件上传校验
PHP文件上传校验
引言
文件上传是网站高频功能,也是最容易出安全问题的地方。只判断后缀名,黑客可以把木马改名为图片绕过,上传到服务器执行。上传必须做多层校验。
必须校验的内容
- 检查上传错误码,确认文件确实上传成功;
- 限制文件大小,防止超大文件占满磁盘;
- 白名单校验扩展名,只允许jpg/png等指定类型;
- 读取文件真实MIME类型,不能只信前端传的type;
- 重新生成随机文件名,避免原文件名覆盖或路径穿越;
- 上传目录禁止执行PHP脚本。
基础校验示例
<?php
$allow = ['jpg','png','gif'];
$ext = strtolower(pathinfo($_FILES['file']['name'],PATHINFO_EXTENSION));
if(!in_array($ext,$allow)){
exit('类型不允许');
}
if($_FILES['file']['size'] > 2*1024*1024){
exit('文件过大');
}
//获取真实类型
$info = getimagesize($_FILES['file']['tmp_name']);
if(!$info) exit('不是合法图片');
//随机重命名
$newName = uniqid().'.'.$ext;
move_uploaded_file($_FILES['file']['tmp_name'],'./upload/'.$newName);
服务端加固
- Nginx/Apache配置上传目录禁止解析php;
- 图片上传后重新压缩生成,破坏木马脚本;
- 关闭上传目录的执行权限;
- 定期扫描上传目录文件。
总结
文件上传安全核心:不信任前端、白名单、校验真实类型、随机改名、目录隔离。多层校验才能防止webshell上传,保护服务器安全。
Git分支管理实践
Git分支管理实践
引言
多人协作开发如果所有人直接往master主分支提交,代码很容易混乱、出现问题无法回滚。一套清晰的分支命名和合并规则,是团队开发协作的基础。
常见分支模型
- master/main:稳定生产分支,只接受合并,不直接开发;
- develop:日常集成开发分支;
- feature/xxx:单个功能开发分支,从develop拉出;
- hotfix/xxx:线上紧急bug修复分支,从master拉出;
- release/x.x:版本发布前测试分支。
常用命令
# 新建并切换功能分支
git checkout -b feature/login
# 提交代码
git add .
git commit -m "新增登录接口"
# 推送到远程
git push origin feature/login
开发流程
- 从develop拉取新功能分支;
- 本地开发提交,定期同步develop最新代码;
- 测试通过后发起合并请求(MR/PR);
- 代码评审通过合并到develop;
- 上线前拉出release分支测试,最终合并master打tag。
注意事项
- 功能分支命名要语义化,带上开发人或需求编号;
- 每次提交信息写清楚改了什么;
- 不要在长期分支堆积大量提交,频繁同步主干避免冲突;
- hotfix修复完成后要同步回develop,防止下次版本再次出现bug。
总结
Git分支模型不追求复杂,适合团队规模即可。核心原则:主干保持稳定,功能独立分支,小步提交,评审合并,线上问题走hotfix单独修复。
Redis哈希结构场景
Redis哈希结构场景
引言
Redis Hash(哈希)适合存储对象类数据,类似数据库单行记录。相比把对象序列化为JSON字符串存储,Hash可以单独读写某个字段,不用整体读取修改,适合用户资料、商品基础信息这类场景。
Hash基础命令
# 设置单个字段
HSET user:100 name "test" age 20
# 获取单个字段
HGET user:100 name
# 获取全部字段
HGETALL user:100
# 删除字段
HDEL user:100 age
PHP操作示例
<?php
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$key = 'user:100';
$redis->hMSet($key,[
'name'=>'admin',
'phone'=>'13800138000'
]);
$info = $redis->hGetAll($key);
print_r($info);
适用与不适用场景
✅ 适合:用户资料、商品基础属性,需要局部更新字段
❌ 不适合:字段数量超大、海量对象;大量独立统计计数,推荐单独key
注意事项
Redis Hash有底层ziplist编码,字段少、值小的时候内存占用极低。当字段数量或者value超过阈值,会转为hash表,内存占用上升。不要在单个hash里面存放成千上万的字段。
总结
Hash是Redis高频数据结构,适合存储结构化小对象,可以局部读写字段,避免JSON字符串每次全量读写。根据业务字段数量合理选择,不要滥用。
Docker多阶段镜像构建
Docker多阶段镜像构建
引言
直接打包项目代码、编译工具到镜像,镜像体积巨大,部署传输慢。Docker多阶段构建可以在第一阶段编译代码,第二阶段只复制编译后的成品,剔除编译器、源码,大幅度减小镜像大小。
原理
一个Dockerfile可以写多个FROM,每一个FROM代表一个阶段。阶段之间可以复制文件,最终镜像只保留最后一个阶段内容,前面阶段的编译环境不会打包进最终镜像。
示例Dockerfile(PHP项目)
#阶段1:构建
FROM php:8.2-cli AS builder
WORKDIR /app
COPY . .
RUN composer install --no-dev
#阶段2:运行环境
FROM php:8.2-cli-alpine
WORKDIR /app
COPY --from=builder /app .
CMD ["php","index.php"]
最终镜像只有php运行环境加业务代码,没有composer、源码缓存文件。
优势
- 镜像体积大幅缩小,传输更快;
- 减少攻击面,镜像内没有编译工具,安全性更高;
- 只维护单个Dockerfile,构建流程简单。
注意事项
- 多阶段命名便于阅读;
- 不要复制不需要的文件,
.dockerignore忽略日志、缓存; - 区分开发镜像与生产镜像,生产镜像精简。
总结
多阶段构建是容器化部署最佳实践,适合PHP、Go、前端打包等场景,生产环境镜像尽量使用多阶段构建,减小体积提升部署效率。
Web XSS攻击分类
Web XSS攻击分类
引言
XSS跨站脚本是最常见的Web漏洞,黑客注入JS代码,在用户浏览器执行,盗取cookie、劫持页面。建站开发必须了解XSS三种类型,针对性做好防护。
XSS三大类型
- 存储型XSS:代码存入数据库,所有访问页面的用户都会触发,危害最大。例如评论区没有过滤,提交恶意js存入数据库。
- 反射型XSS:恶意代码放在URL参数,用户点击链接,页面直接输出参数触发,不会存入数据库。
- DOM型XSS:前端JS直接读取URL参数修改DOM,不经过后端服务,后端日志看不到攻击载荷。
基础防护方案
- 后端输出页面使用htmlspecialchars转义;
- 前端渲染数据不要直接innerHtml,优先textContent;
- 设置Cookie HttpOnly,防止JS读取cookie;
- 配置CSP内容安全策略。
简单示例漏洞代码
<?php
//危险代码,直接输出参数,存在反射XSS
echo $_GET['name'];
//安全写法
echo htmlspecialchars($_GET['name'],ENT_QUOTES);
?>
总结
XSS核心成因:把外部可控数据当成页面代码执行。存储型风险最高,DOM型容易被忽略。前后端配合做输出转义、CSP、HttpOnly Cookie,多层防护抵御XSS。
MySQL慢查询定位
MySQL慢查询定位
引言
网站访问缓慢很多根源是执行时间过长的SQL。开启MySQL慢查询日志,可以捕获执行超时SQL,找到需要优化的语句,是数据库性能排查的核心手段。
慢查询基础配置
慢查询日志会记录执行时长超过指定阈值的SQL语句。
-- 查看慢查询是否开启
SHOW VARIABLES LIKE 'slow_query_log';
-- 设置阈值,单位秒,超过1秒就记录
SET GLOBAL long_query_time =1;
常用分析工具
- mysqldumpslow:mysql自带工具,统计慢日志
mysqldumpslow -s t /var/lib/mysql/slow.log - pt-query-digest:percona工具,分析效果更强,适合大量日志。
优化排查流程
- 开启慢查询,收集慢SQL;
- 使用explain查看执行计划,判断是否缺少索引;
- 优化where条件,添加合适索引,改写SQL逻辑;
- 优化完成后,持续观察慢日志确认效果。
示例
EXPLAIN SELECT * FROM article WHERE title like '%PHP%';
看type字段,ALL代表全表扫描,是需要优化的信号。
总结
慢查询日志用来抓取低效SQL,但是开启会少量增加服务器IO压力,生产环境可以按需开启。定位到慢SQL后优先检查索引,再调整SQL写法,解决数据库卡顿问题。
PHP请求参数过滤
PHP请求参数过滤
引言
Web接口接收GET、POST参数,如果不做过滤校验,容易出现SQL注入、XSS、业务逻辑漏洞。PHP开发接口时,参数过滤是安全第一道防线,所有外部输入都不可信。
基础过滤思路
- 类型强制转换:数字参数直接转为int;
- 长度限制,防止传入超大字符串;
- 正则匹配手机号、邮箱等固定格式;
- 输出到页面时做html转义,防止XSS;
- 入库前转义或者使用PDO预编译防止注入。
代码示例
<?php
// 获取GET参数
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$name = trim($_GET['name'] ?? '');
// 长度校验
if(mb_strlen($name)>50){
exit("参数过长");
}
// 输出到页面,防XSS
echo htmlspecialchars($name,ENT_QUOTES);
框架层面最佳实践
TP6、Laravel等框架自带验证器,推荐优先使用框架验证,不用手写大量if判断。
// ThinkPHP6验证示例
$validate = new \think\Validate([
'id'=>'require|integer',
'name'=>'require|max:50'
]);
避坑点
不要简单依靠addslashes防御注入,推荐PDO预处理语句;参数过滤不要只在前端校验,后端必须二次校验,前端校验可被绕过。
总结
任何外部传入的数据都要校验,区分类型校验、长度校验、格式校验、输出转义四层防护,降低Web安全漏洞风险,是PHP项目开发的基础规范。
Linux查看系统负载
Linux查看系统负载
引言
服务器CPU、内存压力过大,会造成网站接口响应缓慢、502报错。运维排查服务器性能问题第一步就是查看系统负载,了解服务器当前资源压力,及时扩容或者优化程序。
负载概念
uptime输出的三个数字:1分钟、5分钟、15分钟平均负载。代表等待CPU处理的任务数量。
多核CPU参考标准:4核服务器,负载接近4说明CPU打满;超过代表任务排队,系统过载。
常用命令
# 快速查看负载
uptime
# 实时监控
top
# 更轻量化监控
htop
# 查看CPU核心数量
nproc
top命令内可以按1查看每个核心负载;P按CPU使用率排序,快速找到耗资源进程。
排查思路
- 负载高,CPU使用率高:程序代码循环、大量计算,定位进程优化代码;
- 负载高,CPU使用率低:大量IO等待(磁盘读写慢、数据库阻塞),查看磁盘io
iostat。
实操示例
# 安装iostat
yum install -y sysstat
# 持续查看io状态
iostat -x 1
总结
平均负载不只代表CPU,还包含IO等待。看到高负载不能直接判定CPU瓶颈,结合top、iostat综合定位,区分是计算密集还是IO密集导致服务器卡顿。
Nginx防盗链原理
Nginx防盗链原理
引言
网站图片、视频、静态资源经常被其他站点直接引用,消耗服务器流量与带宽。Nginx防盗链通过校验请求来源Referer,阻止第三方网站直接嵌入你的资源链接,降低服务器流量损耗。
原理说明
浏览器访问页面加载图片时,请求头会携带Referer,代表来源页面地址。Nginx读取该请求头,判断域名是否属于本站,非信任域名直接返回403拒绝访问。
局限:Referer可被客户端伪造,防盗链属于基础防护,不是100%安全,重要资源建议搭配token鉴权。
Nginx配置示例
location ~* \.(jpg|png|gif|mp4)$ {
valid_referers none blocked *.0454.wang;
if ($invalid_referer) {
return 403;
}
}
配置解释:
valid_referers配置允许的来源;none允许直接浏览器地址栏访问资源;- 匹配不到白名单,
$invalid_referer生效,返回403。
进阶优化
- 重要资源可使用url签名,生成带时效token的资源地址;
- CDN环境,需要同步在CDN配置防盗链;
- 部分爬虫、APP请求不带Referer,按需调整规则。
总结
Nginx Referer防盗链是低成本的静态资源保护手段,适合图片博客、素材站点。适合作为第一层防护,高安全场景需要结合URL签名二次校验。