Linux压缩解压命令大全,tar、zip、unzip用法
前言
服务器上打包备份、上传下载源码、解压第三方软件,都离不开压缩解压命令。Linux 常用两套工具:tar(配合 gzip/bzip2/xz,后缀 .tar.gz、.tar.bz2、.tar.xz)和 zip/unzip(后缀 .zip,与 Windows 互通方便)。本篇按场景汇总最常用命令,记住几个核心参数即可覆盖日常运维。
一、tar 核心参数记忆
c:创建打包(create)x:解包(extract)t:查看包内容不解压(list)v:显示过程(verbose)f:指定文件名(file,必须放最后)z:gzip 压缩(.tar.gz)j:bzip2 压缩(.tar.bz2)J:xz 压缩(.tar.xz)-C 目录:解压到指定目录--exclude=路径:打包时排除某些目录
二、tar 打包压缩常用命令
# 1. 打包并用 gzip 压缩目录(最常用)
tar -zcvf backup.tar.gz /var/www/html
# 2. bzip2 压缩(压缩率更高,速度慢)
tar -jcvf backup.tar.bz2 /var/www/html
# 3. xz 压缩(压缩率最高,速度最慢)
tar -Jcvf backup.tar.xz /var/www/html
# 4. 打包时排除缓存目录
tar -zcvf site.tar.gz /var/www/html --exclude=/var/www/html/cache --exclude=/var/www/html/logs
# 5. 查看压缩包内容(不解压)
tar -ztvf backup.tar.gz
三、tar 解压常用命令
# 解压 .tar.gz 到当前目录
tar -zxvf backup.tar.gz
# 解压到指定目录(目录需先存在)
mkdir -p /data/restore
tar -zxvf backup.tar.gz -C /data/restore
# .tar.bz2 解压
tar -jxvf backup.tar.bz2
# .tar.xz 解压
tar -Jxvf backup.tar.xz
# 只解压包内某个文件
tar -zxvf backup.tar.gz ./www/config.php
tar 会自动识别压缩格式,日常也可直接用 tar -xvf backup.tar.gz,省略 z/j/J。
四、zip 与 unzip
# 安装(最小化系统可能缺失)
yum install -y zip unzip
# 压缩目录为 zip
zip -r site.zip /var/www/html
# 压缩时排除某些文件
zip -r site.zip /var/www/html -x "*/cache/*" "*.log"
# 解压 zip 到当前目录
unzip site.zip
# 解压到指定目录
unzip site.zip -d /data/restore
# 查看 zip 内容
unzip -l site.zip
# 解压时覆盖已存在文件(不询问)
unzip -o site.zip
参数:-r 递归打包目录,-d 指定解压目录,-o 强制覆盖,-l 列出内容。
五、gzip / gunzip 单文件压缩
# 压缩单个文件(原文件被替换为 .gz)
gzip messages
# 解压
gunzip messages.gz
# 等价于
gzip -d messages.gz
# 保留原文件压缩
gzip -k messages
六、常见坑
f参数位置错:tar -zcvf 文件名中f必须紧跟文件名,放最前面会报错;- 打包用绝对路径:
tar -zcvf a.tar.gz /var/www解压时会还原完整路径;建议先cd /var再打包www,或用相对路径; - 解压目录不存在:
-C指定的目标目录要先mkdir -p,否则解压失败; - zip 忘记
-r:只打包了目录本身,里面文件没进去; .tar.gz当成.zip解压:先file 文件名或tar -ztvf确认格式;- 中文文件名乱码:unzip 加
-O CP936(老版本)或-O gbk,解压 Windows 带来的 zip; - 大文件打包占满磁盘:先确认目标分区剩余空间,备份包不要和源目录放同一分区。
七、备份场景示例
# 打包网站源码并按日期命名
tar -zcvf /backup/site_$(date +%Y%m%d).tar.gz -C /var www
# 打包数据库导出文件
mysqldump -u root -p dbname > db.sql
gzip db.sql
总结
日常备份优先 tar -zcvf 包名.tar.gz 目录,解压 tar -zxvf 包名.tar.gz;与 Windows 互通用 zip -r / unzip;f 参数紧跟文件名,解压前先确认格式和目标目录,打包大目录时注意磁盘空间和路径写法。
遇到问题加QQ23979811 协助处理
Linux查看系统启动时间,uptime命令详解
前言
排查服务器"为什么重启了"、评估系统稳定性时,需要知道系统上次启动时间和连续运行时长。uptime 是最直接的命令,一行同时给出当前时间、运行时长、在线用户数和平均负载。本篇拆解它的输出含义,并补充查看精确开机时间的其他方法。
一、uptime 基本用法
uptime
# 输出示例:
# 07:35:12 up 18:22, 2 users, load average: 0.12, 0.08, 0.05
二、输出字段逐项解读
07:35:12:系统当前时间;up 18:22:系统已连续运行 18 小时 22 分钟;如果跨天较长会显示days,例如up 3 days, 04:10;2 users:当前登录的会话数(包含 ssh、终端、图形会话,重复登录会重复计数,不等于在线人数);load average: 0.12, 0.08, 0.05:系统平均负载,分别对应最近 1 分钟、5 分钟、15 分钟 的平均活跃进程数(含运行、等待 CPU、等待 IO 的进程)。
三、平均负载怎么判断是否偏高
平均负载要看 CPU 核心数才有意义:
# 查看 CPU 核心数
nproc
# 或
cat /proc/cpuinfo | grep "processor" | wc -l
经验判断:
- 负载值 < CPU 核数:CPU 处理得过来;
- 负载 ≈ 核数:接近饱和;
- 负载 > 核数持续几分钟:进程在排队,需要结合
top、vmstat排查是 CPU 密集还是 IO 密集; - 1 分钟高、5/15 分钟低:短时高峰;三分钟趋势接近且都偏高:持续压力。
四、uptime 常用参数
# 简洁模式,只显示运行时长
uptime -s
# 显示上次开机时间(精确到时分秒)
# 输出示例:2026-10-21 13:12:49
uptime -p
# 漂亮格式:up 18 hours, 22 minutes
五、其他查看开机时间的方法
# 1. who -b 查看上次启动时间
who -b
# 2. last reboot 列出历次重启记录(查谁重启、何时重启)
last reboot | head
# 3. 读取 /proc/uptime(第一个值为秒数,第二个为空闲秒数)
cat /proc/uptime
# 4. systemd 系统用 systemd-analyze 看启动耗时
systemd-analyze
六、常见坑
up后面格式看错:18:22是小时:分钟,不是当天时间;跨天会出现days;- users 当成真实人数:它统计会话数,一条 ssh 断线未退出也会占一个会话;用
w或who看具体登录人; - 负载和 CPU 占用混淆:高负载可能是磁盘 IO 等待导致,CPU 使用率反而不高,要结合
top中%wa判断; - 不结合核数看负载:4 核机器负载 2.0 很轻松,1 核机器负载 2.0 已经排队;
uptime -s拿到的是开机时间,不是进程启动时间:查某个服务何时启动用ps -o lstart -p PID。
七、排查组合命令
# 一次看:运行时长、负载、CPU 核数、当前登录人
uptime
nproc
w
服务异常时先看 uptime:若运行时长很短,说明机器刚重启,先确认是计划重启、断电还是崩溃重启,再结合 last reboot 和 /var/log/messages 定位原因。
总结
uptime 一行给出当前时间、已运行时长、登录会话数和 1/5/15 分钟负载;判断负载要除以 CPU 核数;精确开机时间用 uptime -s、who -b、last reboot;重启类故障先确认运行时长和重启记录,再往下查系统日志。
遇到问题加QQ23979811 协助处理
CentOS7 安装wget、vim、net-tools常用工具包
前言
新装的 CentOS7 最小化系统默认不带 wget、vim、net-tools(ifconfig、netstat)、lsof、curl 等常用工具,执行命令时会提示 command not found。本篇汇总这些基础工具包的安装命令、对应包名和常见报错处理,新机器上线后可一次性补齐。
一、先更新缓存
yum clean all
yum makecache
若提示镜像 404 或超时,先更换国内 yum 源(阿里云/清华),再执行安装。
二、常用工具包安装命令
# wget:下载文件
yum install -y wget
# vim:文本编辑器(最小化系统默认只有 vi)
yum install -y vim
# net-tools:提供 ifconfig、netstat、route、arp 等网络工具
yum install -y net-tools
# curl:命令行请求接口
yum install -y curl
# lsof:查看端口/文件被哪个进程占用
yum install -y lsof
# zip/unzip:压缩解压
yum install -y zip unzip
# tar 系统自带;补充常用编译工具链(源码编译时需要)
yum install -y gcc gcc-c++ make
一次性批量安装:
yum install -y wget vim net-tools curl lsof zip unzip gcc gcc-c++ make
三、各工具用途速查
| 工具/包 | 提供命令 | 典型用途 |
|---|---|---|
| wget | wget | 下载文件、整站镜像 |
| vim | vim | 编辑配置文件 |
| net-tools | ifconfig、netstat、route | 查看网卡、端口、路由 |
| curl | curl | 测试接口、下载 |
| lsof | lsof | 查端口占用、删不掉的文件 |
| zip/unzip | zip、unzip | 打包解压 zip |
| gcc/gcc-c++/make | gcc、g++、make | 编译源码 |
四、安装后验证
# 查看版本确认安装成功
wget --version
vim --version
netstat -tlnp
curl --version
lsof -v
netstat -tlnp 能正常输出监听端口列表,说明 net-tools 已装好。
五、常见坑
ifconfig: command not found:不是系统坏了,是没装 net-tools;新系统推荐用ip addr替代;vim: command not found:最小化只带vi,装 vim 才有语法高亮和完整快捷键;wget: command not found:最小化镜像默认不装,单独yum install -y wget;- yum 安装报 404/超时:CentOS7 官方源已逐步归档,先换阿里云 vault 镜像源再装;
- 提示包不存在:确认系统版本(
cat /etc/centos-release),net-tools 在 CentOS7 仓库内,8/9 对应包名基本一致; - 编译安装软件缺依赖:先装
gcc gcc-c++ make,再按报错补pcre-devel、openssl-devel、zlib-devel等开发包。
六、补充:网络工具 iproute2
CentOS7 已默认带 iproute2,提供 ip 命令,功能比 net-tools 更现代:
ip addr # 替代 ifconfig
ip route # 替代 route
ss -tlnp # 替代 netstat -tlnp
老运维习惯 net-tools,新脚本建议优先用 ip 和 ss。
总结
新机器先 yum clean all && yum makecache,再 yum install -y wget vim net-tools curl lsof zip unzip 批量补齐;命令找不到先判断是缺包,用 yum provides 命令名 反查对应包名;网络排查可新旧工具并用,ip、ss 作为现代替代。
遇到问题加QQ23979811 协助处理
Linux查看文件大小,du和df命令区别
前言
排查磁盘满时最容易混淆 du 和 df:一个统计文件实际占用,一个统计分区已用空间,二者数字常常对不上。搞清楚各自口径、配合使用,才能准确找到占空间的目录和被已删除但仍被进程占用的空间。本篇讲清两者区别、常用参数和排查组合拳。
一、df:看分区(文件系统)整体用量
df 读取文件系统超级块,统计每个分区总容量、已用、可用、挂载点,回答的是"这块盘还剩多少"。
# 人类可读格式(K/M/G),显示文件系统类型
df -hT
# 只看某个挂载点
df -h /var
# 列出 inode 使用情况(空间没满但报 No space 时排查)
df -i
参数:-h 人类可读,-T 显示类型,-i 查看 inode,-x tmpfs 排除内存临时文件系统。
二、du:统计目录/文件实际占用
du 遍历目录树累加每个文件大小,回答的是"哪个目录最占地方"。
# 查看当前目录大小,人类可读
du -sh .
# 查看 /var 下一级子目录大小并排序(从大到小)
du -h --max-depth=1 /var | sort -rh | head -20
# 查看单个文件大小
du -h /var/log/messages
# 统计当前目录下各子目录,只显示总计
du -sh /var/*
参数:-s 只显示汇总,-h 人类可读,--max-depth=N 限定统计层级,-x 不统计其他挂载点。
三、两者核心区别
| 对比项 | df | du |
|---|---|---|
| 统计对象 | 文件系统分区整体 | 目录/文件实际大小 |
| 数据来源 | 超级块分配/使用记录 | 遍历文件累加 |
| 回答问题 | 盘还剩多少 | 哪个目录占空间 |
| 典型用途 | 判断是否磁盘满、inode 耗尽 | 定位大目录、大文件 |
四、为什么 du 和 df 数字对不上
常见三种原因:
- 已删除文件仍被进程占用:文件删掉了但进程没释放句柄,
du不再统计,df仍计入已用。用lsof | grep deleted找到占用进程,重启或截断对应文件即可释放。 - 硬链接重复统计口径:du 按目录累加,硬链接在不同目录可能被重复计数;df 看块设备分配更准。
- 隐藏在挂载点下的旧文件:目录里先写了数据,后在该路径挂载新盘,旧文件被遮盖,du 看不到,df 上一层分区仍占用。
五、磁盘满排查标准流程
# 1. 先用 df 确认哪个分区满了、inode 是否耗尽
df -h
df -i
# 2. 进入对应分区,逐层 du 找大目录
du -h --max-depth=1 /var | sort -rh | head
# 3. 找大于 500M 的文件
find /var -type f -size +500M -exec ls -lh {} \;
# 4. 检查被删除但仍占用空间的文件
lsof | grep deleted
定位到日志、缓存、旧备份等大文件后,优先归档或压缩,不要直接 rm 正在被写入的日志,应截断:
# 清空正在写入的日志而不释放句柄冲突
> /var/log/nginx/access.log
六、常见坑
- 只 du 不 df:看完目录不大,却忽略分区已 100%;
du -sh /统计到其他挂载点:加-x限定当前文件系统;- 误删正在写入的大日志:空间不释放,应先重启对应服务或用
>截断; - 只看磁盘空间忽略 inode:小文件过多时空间有剩但创建文件失败,用
df -i排查; - 权限不足漏统计:普通用户 du 部分目录读不到,排查时用 root。
总结
df -h 看分区用量和剩余,du -sh --max-depth=1 目录 逐层定位大目录;两者数字不一致时优先怀疑"已删除但被进程占用"的文件,用 lsof | grep deleted 核对。排查顺序:先 df 定位分区,再 du 找目录,最后 find 找具体大文件。
遇到问题加QQ23979811 协助处理
Linux文件权限755、644含义,chmod修改权限
前言
部署网站时常遇到 Nginx 报 403、PHP 无法写入上传目录,根子多半是文件/目录权限不对。理解 Linux 三组身份(属主、属组、其他)和 rwx 三位数字,再用 chmod/chown 调整,问题基本都能解决。本篇讲清 755、644 等常用权限数字的含义和修改方法。
一、先看权限:ls -l
ls -l index.php
# 输出示例:-rw-r--r-- 1 www www 1234 Oct 22 07:20 index.php
第一位 - 是文件类型(d 目录、l 软链接);后面9位分三组:
| 分组 | 含义 | 对应数字位 |
|---|---|---|
| 前三位 | 文件属主 user | 第1位 |
| 中间三位 | 文件属组 group | 第2位 |
| 后三位 | 其他用户 other | 第3位 |
二、rwx 与数字对应关系
r读 = 4w写 = 2x执行 = 1-无权限 = 0
把每组数字相加得到该组权限:
rwx= 4+2+1 = 7rw-= 4+2+0 = 6r-x= 4+0+1 = 5r--= 4+0+0 = 4
三、常用权限数字含义
- 644 文件:属主读写,属组和其他只读。普通网页文件、配置文件标准权限。
- 755 目录 / 可执行脚本:属主读写执行,属组和其他读+执行。目录必须有
x才能进入,脚本必须有x才能执行。 - 777 所有人全权:方便但不安全,仅临时调试用,网站目录绝不要长期开放 777。
- 600 私密文件:只有属主能读写,适合存放密钥、配置里的密码文件。
目录规范建议:目录 755,普通文件 644,可执行脚本 755。
四、chmod 修改权限
# 给单个文件设644
chmod 644 index.php
# 给目录设755
chmod 755 /var/www/html
# 递归修改目录及其下所有文件权限(注意目录和文件权限要分开处理)
chmod -R 755 /var/www/html
符号写法(叠加/去除):
# 给属主加执行权限
chmod u+x script.sh
# 给组和其他去除写权限
chmod go-w index.php
# 属组可写
chmod g+w /data
五、chown 属主属组
权限对了但属主不对,PHP 进程仍可能写不进去:
# 修改属主为 www
chown www index.php
# 同时改属主和属组
chown www:www /var/www/html
# 递归修改
chown -R www:www /var/www/html
六、常见坑
- 目录没加 x 权限:目录权限 644 会导致无法
cd进入和列目录,网页报 403; - 把文件也设成 755/777:普通文件不需要执行位,777 会放大安全风险;
- 递归时文件目录一刀切:用
find分开处理目录和文件,见下方命令; - 只改权限不改属主:Nginx/PHP-FPM 以 www 用户运行,文件属主是 root 时仍读不到;
- SELinux 拦截:CentOS 上权限看似正确仍 403/500,要检查 SELinux 上下文。
目录与文件分开递归:
# 目录全部755
find /var/www/html -type d -exec chmod 755 {} \;
# 文件全部644
find /var/www/html -type f -exec chmod 644 {} \;
总结
r=4 w=2 x=1,文件常用 644、目录常用 755;chmod 改权限、chown 改属主属组;递归时目录和文件权限分开设置,网站目录不要长期用 777。
遇到问题加QQ23979811 协助处理
Linux创建用户、删除用户、修改密码完整命令
前言
服务器上为不同人员或程序分配账号,是Linux基础运维的常见操作。创建账号要顺便建好家目录、设好初始密码;删除账号要决定是否清掉家目录和邮件;改密码分root改别人密码和用户改自己密码两种场景。本篇把常用命令一次讲全。
一、创建用户
# 新建用户,同时创建家目录 /home/www
useradd -m www
# 指定UID、家目录、登录Shell、所属组
useradd -u 1001 -d /home/www -s /bin/bash -G wheel www
# 设置初始密码(交互式两次输入)
passwd www
参数说明:
-m:自动创建/home/用户名家目录;-s:指定登录Shell,不想登录给/sbin/nologin;-G:附加到指定组;-M:不创建家目录(服务账号常用)。
一键创建并设密码(脚本场景,免交互):
useradd -m deploy
echo 'Deploy@123456' | passwd --stdin deploy
二、查看用户信息
# 查看用户UID、GID、所属组
id www
# 查看 /etc/passwd 里该用户记录
grep www /etc/passwd
# 查看组信息
groups www
三、修改密码
# root修改任意用户密码(交互式)
passwd www
# 用户自己改自己的密码
passwd
# 强制该用户下次登录改密码
passwd -e www
# 锁定账号(禁止登录)
usermod -L www
# 解锁
usermod -U www
四、修改用户属性
# 改用户名
usermod -l newname oldname
# 改登录Shell
usermod -s /sbin/nologin www
# 加入sudo组(CentOS为wheel)
usermod -aG wheel www
五、删除用户
# 只删账号,保留家目录和邮件
userdel www
# 连家目录、邮件一起删除(推荐,避免残留占空间)
userdel -r www
# 若提示用户进程仍在运行,先终止相关进程
pkill -u www
删除前确认该用户名下没有正在跑的重要服务,否则会误停业务。
六、常见坑
- 服务账号没设
/sbin/nologin:容易被登录爆破,数据库、nginx这类程序账号应禁止Shell登录; userdel没加-r:家目录残留,多次建删会堆很多无用目录;- 改了用户名没改家目录属主:
chown -R www:www /home/www补一下; - 密码太简单被拒绝:
passwd报错BAD PASSWORD,可强制用echo ... | passwd --stdin绕过复杂度检查(生产环境仍应设强密码); - 服务账号误删:先
id和ps -u www确认归属再删。
总结
useradd -m 用户名 建号、passwd 用户名 设密码;root用 passwd 用户名 改任意账号密码,用户用 passwd 改自己的;userdel -r 用户名 连家目录一起删,删前先确认该账号名下没有运行中的服务。
遇到问题加QQ23979811 协助处理
CentOS7 yum安装软件报错,yum源更换教程
前言
CentOS7执行 yum install 经常报错:Could not resolve host、No package available、Download failed、Metadata cache expired。原因大多是默认国外源慢或已失效、DNS不通、缓存过期。换成国内阿里云/清华镜像源,再清缓存重建,基本都能解决。本篇给出完整换源步骤。
一、先判断报错类型
# 看当前yum源列表
yum repolist
# 测试网络与DNS
ping -c 3 mirrors.aliyun.com
nslookup mirrors.aliyun.com
- 解析不了域名:先修DNS(
/etc/resolv.conf加nameserver 114.114.114.114); - 能ping通但装不上:源本身失效,换国内源。
二、备份原有源
cd /etc/yum.repos.d/
mkdir backup
mv CentOS-Base.repo backup/
保留备份,换源失败可还原。
三、更换阿里云Base源(CentOS7)
# 下载阿里云7镜像源配置
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
# 若curl不可用用wget
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
清华源备选:
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.tuna.tsinghua.edu.cn/repo/Centos-7.repo
四、更换EPEL源(装扩展软件常用)
# 先删旧epel
rm -f /etc/yum.repos.d/epel.repo
# 阿里云epel7
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo
五、清缓存并重建
# 清除旧缓存
yum clean all
# 重建缓存
yum makecache
# 验证源是否生效
yum repolist
# 再试安装
yum install -y vim wget net-tools
六、常见坑
- CentOS8/9换了7的源:版本号要对应,
Centos-7.repo只用于7; - 下载源文件404:镜像站路径改版,换清华或官方备用地址;
- 多个repo残留冲突:
/etc/yum.repos.d/下旧的失效repo没删干净,会重复报错; - makecache卡在Metadata:DNS或网络不通,先
ping镜像站确认; - yum install -y 卡住超时:可临时调大超时,或换网络更好的镜像;
- 时间不准导致HTTPS证书校验失败:先
date检查系统时间,用ntpdate校时。
总结
yum报错先分DNS不通还是源失效:DNS不通修resolv.conf;源失效就备份 /etc/yum.repos.d/,下载阿里云Centos-7.repo和epel-7.repo,yum clean all && yum makecache 重建缓存后再安装。
遇到问题加QQ23979811 协助处理
PHP密码加密存储
PHP密码加密存储
引言
用户密码绝不能明文入库,泄露即灾难。MD5、SHA1是哈希算法但速度快、可被彩虹表破解,不适合存密码。PHP 5.5以后内置password_hash和password_verify,用bcrypt自带盐值和成本因子,是现在注册登录系统的标准做法。
注册:生成哈希入库
$pwd = $_POST['password'];
$hash = password_hash($pwd, PASSWORD_DEFAULT);
// $2y$10$.... 直接把这串哈希存数据库
$db->insert('users', ['password' => $hash]);
PASSWORD_DEFAULT默认bcrypt,自动生成随机盐,算法和成本因子都记录在哈希串里,不需要单独存盐字段。
登录:校验密码
$row = $db->find('users', ['username' => $username]);
if ($row && password_verify($inputPwd, $row['password'])) {
// 密码正确,登录成功
} else {
// 用户名或密码错误
}
password_verify自动识别哈希里的算法和盐,比对用户输入的明文,返回布尔值。
指定成本因子
$hash = password_hash($pwd, PASSWORD_BCRYPT, ['cost' => 12]);
cost越高计算越慢、抗暴力破解越强,默认10。服务器性能允许可适当调高,太慢会影响登录接口响应。
判断哈希是否需要升级
if (password_needs_rehash($row['password'], PASSWORD_DEFAULT)) {
$newHash = password_hash($inputPwd, PASSWORD_DEFAULT);
$db->update('users', ['password' => $newHash], $row['id']);
}
算法或成本升级后,老用户登录时校验通过顺便重算哈希,平滑迁移不用强制改密。
错误做法对比
# 不要这样
md5($pwd); # 无盐,彩虹表秒破
sha1($pwd . $salt); # 自制盐方案,仍可GPU爆破
crypt($pwd); # 旧接口,参数难控
自己拼盐、用通用哈希函数存密码都是安全隐患,直接用内置密码函数。
注意事项
- 数据库密码字段长度留够60字符以上,bcrypt哈希约60位;
- 别自己实现加盐哈希,密码函数已封装随机盐和算法版本;
- 登录失败统一提示"用户名或密码错误",别区分哪个不存在;
- 密码强度前端后端双重校验,长度、复杂度、常见弱密码黑名单;
- 哈希不可逆,忘记密码只能重置,不能找回原密码;
- 配合HTTPS传输,密码在传输层也要加密保护。
总结
注册用password_hash生成bcrypt哈希入库,登录用password_verify校验,自带随机盐无需单独存盐。cost成本因子按服务器性能调整,password_needs_rehash在用户登录时平滑升级旧哈希。禁止用MD5、SHA1或自制盐方案存密码,密码字段预留60字符以上,登录失败提示统一,配合HTTPS和密码强度校验构成完整账号安全体系。
Redis自增计数
Redis自增计数
引言
浏览量、点赞数、库存扣减、日活统计这类计数场景,用Redis的INCR原子自增比MySQL计数器性能高得多。单条命令在内存里完成加一,天然原子,并发下不会丢计数,是高并发计数的首选方案。
INCR 自增一
INCR article:1001:views
键不存在先按0初始化再加一,返回自增后的最新值,原子操作,并发安全。
INCRBY 指定步长
INCRBY score:user:2001 10
一次加指定整数,适合批量加分、累计积分场景。
DECR 与 DECRBY 递减
DECR stock:item:88
DECRBY stock:item:88 5
库存扣减用递减,扣到0前配合判断,避免出现负数库存。
读取当前计数
GET article:1001:views
返回字符串形式的数字,业务层转整型使用。
计数加过期时间
INCR day:uv:20261022:user:3001
EXPIRE day:uv:20261022:user:3001 86400
日活、每日点击这类临时计数,自增后设置过期时间,自动清理不占内存。
PHP 代码示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$views = $redis->incr('article:1001:views');
echo '当前浏览量:' . $views;
PHP扩展里incr对应INCR,decr对应DECR,返回自增后整数。
注意事项
- INCR只对字符串数字生效,非数字值会报错;
- 计数要持久化到MySQL时定时批量回写,别每次都写库;
- 计数器长期不清理会膨胀,临时计数务必加TTL;
- 库存扣减要先判断再递减,或用Lua脚本保证判断与扣减原子;
- 分布式下INCR是单key原子,跨多个key计数仍需事务或Lua;
- 计数初始值可用SET先设定,避免从0起步不符合业务。
总结
INCR原子自增一,INCRBY按步长增加,DECR/DECRBY递减,GET读取当前值。浏览量、点赞、积分、库存等高并发计数用Redis内存操作替代MySQL计数器,性能提升明显。临时日活计数自增后配EXPIRE自动清理,重要计数定时批量回写数据库。库存扣减建议用Lua脚本保证判断与扣减的原子性,避免超卖。
Linux查看主机名
Linux查看主机名
引言
多台服务器排障、日志定位、主机识别时先确认主机名。hostname命令最直接,hostnamectl看完整静态/瞬态/灵活名,cat /etc/hostname看持久化配置,三者来源不同,排障时别混。
查看当前主机名
hostname
一行输出当前系统主机名,登录终端提示符里显示的就是它。
hostnamectl 详细信息
hostnamectl
显示静态主机名、瞬态主机名、灵活主机名,还有操作系统、内核、架构等信息,CentOS7以上通用。
查看持久化配置
cat /etc/hostname
这里保存的是重启后生效的主机名,临时hostname改了不落盘,重启会回到这里的值。
查看IP对应名称
hostname -I
输出本机所有非回环IP,配合主机名做主机识别。
临时修改(重启失效)
hostname web-server-01
当前会话立即生效,重启后被/etc/hostname覆盖。
永久修改
hostnamectl set-hostname web-server-01
写入/etc/hostname,重启后保留;改完重连SSH或执行bash使提示符刷新。
注意事项
- 主机名建议用小写字母、数字、中划线,别用中文和下划线;
- 主机名含点号会被当成FQDN,短名和全名分开看;
- 改完主机名记得同步/etc/hosts里127.0.0.1映射,避免部分解析异常;
- 容器内hostname通常跟随容器名,重启重建会变;
- 集群里主机名要规范统一,便于日志检索和监控告警;
- 临时hostname与静态名不一致时,以配置文件为准。
总结
hostname查看当前主机名,hostnamectl看完整主机名分类和系统信息,cat /etc/hostname看持久化配置,hostname -I看本机IP。临时用hostname改名立即生效但重启失效,永久用hostnamectl set-hostname写入配置。主机名规范用小写加中划线,修改后同步/etc/hosts映射,集群命名统一便于日志检索与监控告警。
MySQL查看表行数
MySQL查看表行数
引言
评估表规模、判断是否需要分表、统计数据量时都要看行数。COUNT(*)最准但大表慢,information_schema的TABLE_ROWS快但InnoDB是估算值,两个手段按场景选。
精确统计:COUNT(*)
SELECT COUNT(*) FROM users;
精确返回真实行数,MyISAM直接读元数据很快,InnoDB要逐行扫描,大表千万级以上会锁读、耗时长。
带条件统计
SELECT COUNT(*) FROM orders WHERE status = 1;
统计满足条件的行数,WHERE命中索引时走索引扫描,比全表快。
估算行数:information_schema
SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'blog' AND TABLE_NAME = 'users';
秒出结果,适合先摸底表规模,但InnoDB的TABLE_ROWS是基于采样的估算值,可能和真实值差10%到20%。
一次查看库里所有表行数
SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH/1024/1024 AS data_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'blog'
ORDER BY TABLE_ROWS DESC;
按行数排序,快速找出最大的几张表,配合DATA_LENGTH看占用空间。
大表COUNT优化思路
- 先看估算值判断量级,需要精确再COUNT;
- COUNT(*)走二级索引,别COUNT(主键)以外的可空字段;
- 历史归档、分表后单表控制在千万行以内;
- 实时统计用缓存或汇总表,不在线上大表直接COUNT。
注意事项
- InnoDB的COUNT(*)不加WHERE返回约数,精确值必须扫描;
- information_schema的TABLE_ROWS只是估算,别当准确值用于对账;
- 大表COUNT期间会产生一致性读,占用缓冲池,避开高峰;
- 表刚大量增删后元数据统计信息可能陈旧,先ANALYZE TABLE刷新;
- MyISAM的TABLE_ROWS是精确值,和InnoDB机制不同;
- 统计行数别用COUNT(1)和COUNT(*)纠结,性能基本一致。
总结
COUNT()精确统计行数,大InnoDB表慢且耗资源;information_schema.TABLE_ROWS秒出估算值用于摸底。先看估算判断表规模,需要精确再COUNT(),条件统计尽量走索引。大表COUNT避开业务高峰,增删数据后先ANALYZE TABLE刷新元数据。实时报表建议走汇总表或缓存,避免线上大表反复全表扫描。
PHP检查文件是否存在
PHP检查文件是否存在
引言
上传头像、读取配置、缓存命中判断之前,都要先确认文件是否存在。PHP里file_exists判断文件或目录,is_file专门判断普通文件,is_dir判断目录,三者语义不同,用错会把目录当成文件导致后续读取报错。
file_exists 判断存在
$path = './config/config.php';
if (file_exists($path)) {
echo '文件存在';
} else {
echo '文件不存在';
}
文件或目录存在都返回true,路径不存在、权限不可访问返回false。
is_file 判断是普通文件
if (is_file($path)) {
echo '这是一个普通文件';
}
目录、符号链接、管道都返回false,读取文件前用它最稳妥。
is_dir 判断目录
$dir = './uploads';
if (is_dir($dir)) {
echo '目录存在';
}
专门判断目录,适合上传前检查目录是否已建。
判断可读性再操作
if (is_file($path) && is_readable($path)) {
$content = file_get_contents($path);
}
存在不代表可读,权限不足时file_get_contents会警告,先加is_readable判断。
路径拼接注意
$file = __DIR__ . '/../data/' . $name;
用绝对路径拼接,避免相对路径受当前工作目录影响,跨目录包含时不容易找错文件。
注意事项
- file_exists缓存了文件状态,删改文件后可用clearstatcache清缓存;
- 判断普通文件用is_file,别只靠file_exists,目录会误判;
- 检查存在后还要is_readable/is_writable,权限是另一道关;
- 文件名含中文或特殊字符注意编码统一,避免误判不存在;
- 防路径穿越,用户传入的文件名先过滤..和绝对路径;
- 大量循环判断文件时注意stat缓存带来的性能影响。
总结
file_exists判断文件或目录是否存在,is_file判断普通文件,is_dir判断目录,is_readable/is_writable判断读写权限。读取配置、上传文件、缓存命中前先做存在性和权限判断。注意clearstatcache清理状态缓存,用户传入文件名要防路径穿越,跨目录用DIR拼绝对路径,避免相对路径偏移导致找错文件。
Linux查看进程占用内存最高,kill进程方法
前言
服务器内存吃紧、Swap被刷爆,往往是某个程序内存泄漏或卡死进程占着不放。要先找到"谁在吃内存",确认后再决定是优雅重启还是直接kill。本篇给出从查进程到杀进程的完整命令。
一、按内存占用排序找进程
ps 一次性列出并排序,输出RES(实际物理内存,单位KB):
# 按内存占用从高到低,取前10
ps aux --sort=-%mem | head -10
# 按CPU占用排序
ps aux --sort=-%cpu | head -10
列说明:USER 进程用户、%MEM 内存占比、RSS 实际物理内存、COMMAND 启动命令。
更直观的内存排行:
# 显示内存占用最高的进程,VSZ/RSS单位KB
ps -eo pid,ppid,user,%mem,%cpu,rss,comm --sort=-rss | head
二、用top/htop交互查看
top
# 进入后按 M 按内存排序,按 P 按CPU排序,k 输入PID杀进程,q退出
装个 htop 更清楚:
yum install -y htop
htop
三、按名字批量找进程PID
# 查php-fpm相关进程
pgrep -af php-fpm
# 或
ps -ef | grep php-fpm | grep -v grep
pgrep 只输出PID,-a 带命令行。
四、kill进程(先温柔再强杀)
# 正常结束(SIGTERM=15),给进程清理机会
kill PID
# 按名字结束所有同名进程
killall php-fpm
pkill -f "php think"
# 杀不掉再强制杀(SIGKILL=9),数据可能损坏,最后手段
kill -9 PID
信号优先级:
kill -15(默认):请求退出,进程可捕获;kill -9:直接杀死,无法拦截。
服务类进程优先用它自己的重启命令而不是裸kill:
systemctl restart php-fpm
五、常见坑
- 只看%MEM不看RSS:共享内存会被重复算,判断真实占用看RSS;
- 上来就kill -9:数据库、日志服务强杀可能坏数据,先15再9;
- 杀错进程:杀之前用
ps -p PID -o cmd确认这个PID到底是谁; - 僵尸进程杀不掉:状态
Z的僵尸要杀它的父进程,僵尸本身已经不占CPU只占PID表; - kill完又自动起来:被systemd/守护进程托管,要用
systemctl stop或它的管理命令,否则白杀。
总结
ps aux --sort=-%mem | head 或 top 按M排序找内存大户,确认PID后先用 kill(15)优雅结束,不行再 kill -9。服务类进程优先 systemctl restart,避免强杀造成数据损坏。
遇到问题加QQ23979811 协助处理
CentOS7重启网络报错,network与NetworkManager冲突处理
前言
CentOS7改完网卡配置后 systemctl restart network 报错,常见提示是 "Job for network.service failed because the control process exited with error code"。根源往往是老的 network 服务和新的 NetworkManager(简称NM)同时管一块网卡,配置互相打架。本篇讲清楚怎么判断、怎么处理。
一、先看报错到底是什么
# 重启网络看具体错误
systemctl restart network
# 查看详细失败原因
systemctl status network -l
journalctl -xe | grep network
# 看网卡配置有没有写错
cat /etc/sysconfig/network-scripts/ifcfg-ens33
常见报错:配置里 NM_CONTROLLED=no 与NM冲突、ONBOOT、网关或IP写重复、网卡名和配置文件名对不上。
二、方案一:服务器纯命令行,关掉NetworkManager
服务器一般用不到图形网络管理,直接停用NM,让network接管最干净:
# 停止并禁用NetworkManager
systemctl stop NetworkManager
systemctl disable NetworkManager
# 再重启network
systemctl restart network
同时确认网卡配置里写:
NM_CONTROLLED=no
这样两块不会抢同一张网卡。
三、方案二:保留NetworkManager,用它管网络
如果是桌面或需要NM的场景,就别用老network:
# 查看NM识别到的连接
nmcli connection show
# 重启某块网卡连接
nmcli connection down ens33 && nmcli connection up ens33
# 查看设备状态
nmcli device status
注意:用NM管理时,配置尽量通过 nmcli 或nmtui改,不要手改ifcfg后又让network重启。
四、常见配置文件检查项
/etc/sysconfig/network-scripts/ifcfg-ens33:
TYPE=Ethernet
BOOTPROTO=static
DEFROUTE=yes
NAME=ens33
DEVICE=ens33
ONBOOT=yes
IPADDR=192.168.1.100
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
DNS1=114.114.114.114
NAME/DEVICE要和实际网卡名一致;- 改完配置重启前,保留一个已登录SSH会话,防止网络断了把自己关外面。
五、常见坑
- network和NetworkManager同时重启:两个服务都抢网卡,IP时有时无,二选一;
- 网卡名变了配置文件没改:新系统网卡名是ens33不是eth0,文件名和DEVICE要对应;
- GATEWAY写了两个:多网卡配多个网关会路由冲突;
- 改完只restart不验证:用
ping、ip addr确认IP真的起来了; - 云服务器别乱关NM:部分云镜像靠NM配内网,关了可能丢内网,先确认再操作。
总结
重启网络报错,先 systemctl status network -l 看原因;服务器场景停掉并禁用NetworkManager、配置里 NM_CONTROLLED=no,再restart network;桌面场景改用nmcli管理。二选一,不要两个服务同时管网卡。
遇到问题加QQ23979811 协助处理
Linux日志过大占满磁盘,logrotate日志切割配置
前言
网站跑久了,Nginx、MySQL、PHP-FPM的日志几个G地涨,df -h一看磁盘被日志占满,服务直接报错。手动删不是长久之计,logrotate 是Linux自带的日志切割工具,按天/按大小自动切分并保留指定份数,配一次永久省心。
一、logrotate工作原理
logrotate本身靠系统定时任务(/etc/cron.daily/logrotate)每天跑一次,读取配置:
- 全局配置:
/etc/logrotate.conf - 自定义配置目录:
/etc/logrotate.d/
我们一般在/etc/logrotate.d/下为某个服务单独写配置。
二、以Nginx日志为例配置
新建 /etc/logrotate.d/nginx:
/var/log/nginx/*.log {
daily # 按天切割
missingok # 日志不存在不报错
rotate 14 # 保留14份历史
compress # 切割后gzip压缩
delaycompress # 延迟一次再压缩,避免影响正在写的文件
notifempty # 空日志不切割
create 0644 nginx nginx # 新建日志文件属主权限
sharedscripts # 多个日志只执行一次postrotate
postrotate
if [ -f /run/nginx.pid ]; then
kill -USR1 `cat /run/nginx.pid`
fi
endscript
}
关键点:切割后要给Nginx发USR1信号,让它重新打开日志文件,否则还会往旧文件写。
三、按大小切割(不限时间)
如果日志涨得快,按大小更合理:
/var/log/nginx/error.log {
size 100M # 超过100M切
rotate 10
compress
missingok
notifempty
create 0644 nginx nginx
postrotate
kill -USR1 `cat /run/nginx.pid`
endscript
}
四、测试与立即执行
不等到定时,手动试运行(调试用 -d 打印过程不真正执行):
# 调试,看会做什么
logrotate -d /etc/logrotate.d/nginx
# 强制立即切割一次
logrotate -f /etc/logrotate.d/nginx
切割后原日志变成 xxx.log-20261021,隔天再压成 .gz。
五、常见坑
- 切割后服务还往旧文件写:缺postrotate里的
kill -USR1,或pid路径写错,导致旧文件越来越大; - 直接rm删正在写的日志:空间没释放,要
> 文件清空或重启服务; - compress和delaycompress一起用仍报错:第一次切出的文件先不压,第二次才压,别误以为没生效;
- 日志目录属主不对:create指定的用户组要真实存在,否则新建文件失败;
- cron没跑:确认
crond服务开机自启,否则logrotate永远不触发。
总结
在 /etc/logrotate.d/ 为每个大日志写一份切割规则,配好daily/size、rotate份数和postrotate信号重开日志,logrotate -f 验证一次,磁盘就不会再被单个日志吃满。
遇到问题加QQ23979811 协助处理
PHP合并数组方法
PHP合并数组方法
引言
合并两个或多个数组是日常开发高频操作。索引数组用array_push或加号,关联数组用array_merge,+号和array_merge在键冲突时行为完全不同,搞混会踩坑。
array_merge 合并
$a = [1, 2];
$b = [3, 4];
$c = array_merge($a, $b);
// [1, 2, 3, 4]
索引数组会重排数字键,关联数组同名键后者覆盖前者。
加号 + 保留原键
$a = ['a' => 1, 'b' => 2];
$b = ['b' => 99, 'c' => 3];
$c = $a + $b;
// ['a'=>1,'b'=>2,'c'=>3] 左侧优先
+号左边数组的键优先,同键不覆盖,保留左侧值。
array_merge vs + 的区别
$a = ['a' => 1];
$b = ['a' => 99];
array_merge($a, $b); // ['a'=>99] 后者覆盖
$a + $b; // ['a'=>1] 左侧保留
关联数组合并要谁覆盖谁,选对函数很关键。
数组合并并去重
$all = array_unique(array_merge($a, $b));
合并后去重,适合标签、权限码这类集合。
追加元素到数组
$list[] = 'new';
array_push($list, 'a', 'b');
单元素直接[]追加最简洁,多元素用array_push。
注意事项
- array_merge会重置数字索引,+号保留原数字键;
- 关联数组同名键,array_merge后者覆盖,+号左侧优先;
- 合并大数组注意内存,别在循环里反复merge;
- array_merge递归合并多维要用array_replace_recursive;
- 空数组合并不会报错;
- 合并后记得检查是否都是数组类型再合并。
总结
array_merge合并数组,数字键重排、关联键后者覆盖;+号运算符左侧优先不覆盖。合并去重用array_unique包一层,追加元素直接[]最简洁。注意两者在同名键上的覆盖方向完全相反,多维递归合并用array_replace_recursive,大数组合并注意内存别在循环里反复操作。
Redis删除键命令
Redis删除键命令
引言
清理缓存、下线业务时要删Redis键。DEL是基础删除,UNLINK异步删大键不阻塞主线程,别在线上KEYS加DEL一把删,那会把Redis打挂。
删除指定键
DEL user:1001
删除一个或多个键,返回删除个数,键不存在返回0。
一次删多个
DEL user:1001 user:1002 user:1003
跟多个键名,一次命令批量删,比循环DEL省网络往返。
异步删大键:UNLINK
UNLINK bigkey:list
大key(百万级list/hash)用UNLINK,后台异步释放内存,不阻塞主线程。
判断键是否存在再删
EXISTS user:1001
DEL user:1001
EXISTS返回1存在,删除前确认避免误删。
按模式删:别用KEYS
# 危险:线上禁用
# KEYS user:* | DEL
KEYS会遍历全库阻塞生产,按模式删除用SCAN渐进扫描再UNLINK。
删除整个库
FLUSHDB # 清当前库
FLUSHALL # 清所有库
高危命令,线上几乎不用,执行前再三确认。
注意事项
- DEL是同步删除,大key会阻塞,大key用UNLINK;
- 不要在生产用KEYS模式匹配删除,会卡死Redis;
- 删除不影响持久化AOF/RDB里已写的文件,重启后残留;
- 删错键没有回收站,重要数据先备份;
- 集群下DEL只删当前节点,跨槽键要逐个节点删;
- 定期删缓存要加随机TTL,别一次删一堆热点key。
总结
DEL删一个或多个键,UNLINK异步删大key不阻塞主线程,EXISTS先判断存在。线上禁止KEYS加DEL模式删除,要用SCAN渐进扫描。FLUSHDB清当前库、FLUSHALL清全库属高危操作。删除大key优先UNLINK,批量删缓存错开时间避免雪崩,删错无回收站需提前备份。
Linux查看系统时间
Linux查看系统时间
引言
日志时间对不上、证书过期误判、定时任务不对点,常常是系统时间不准导致的。运维第一件事就是date看时间、timedatectl看时区,服务器必须用NTP同步,否则业务时间戳全乱。
查看当前时间:date
date
输出年月日、时分秒和时区,最常用。
自定义格式输出
date +"%Y-%m-%d %H:%M:%S"
# 2026-10-21 07:03:00
脚本里取指定格式时间做日志前缀。
查看时间戳
date +%s
输出当前Unix时间戳,排查时间差时用。
查看时区与NTP状态
timedatectl
显示本地时间、UTC、时区、NTP同步是否开启,比date信息全。
硬件时间 vs 系统时间
hwclock --show
读主板RTC硬件时钟,系统时间和硬件时间不一致会导致重启后时间跳变。
手动同步一次
timedatectl set-ntp true
# 或
ntpdate ntp.aliyun.com
开启NTP自动同步,国内用阿里云或ntp.aliyun.com源。
注意事项
- 云服务器一般已开NTP,别手动硬改时间;
- 时间跳变会影响日志排序和证书有效期判断;
- 容器内时间默认跟宿主机,宿主机准了容器就准;
- 时区不对用timedatectl set-timezone Asia/Shanghai;
- 不要随便set-time往前拨,数据库主从时间差会出问题;
- 定时任务执行异常先查系统时间和时区。
总结
date看当前时间、date+%s取时间戳、timedatectl看时区和NTP状态、hwclock看硬件时钟。服务器开启NTP自动同步,时区设Asia/Shanghai,时间不对先查NTP和时区,别手动硬改,时间跳变会影响日志、证书和数据库主从,定时任务异常优先核对系统时间。
MySQL删除数据库
MySQL删除数据库
引言
下线测试库、清理废弃项目时需要DROP DATABASE。这是高危操作,一旦执行库和所有表立即删除不可回滚,必须先确认库名、确认备份,避免误删生产库。
删除指定数据库
DROP DATABASE db_test;
删除整个库及其下所有表、视图、存储过程,磁盘空间立即释放。
库存在才删除
DROP DATABASE IF EXISTS db_test;
加IF EXISTS库不存在时不报错,脚本里更安全,避免中断。
删除前先确认
SHOW DATABASES LIKE 'db_test';
USE db_test;
SHOW TABLES;
先列出库和表,确认删的是目标库而不是同名生产库。
删除后验证
SHOW DATABASES;
库从列表消失即删除成功;磁盘空间由MySQL异步释放,大库可能需等一小会。
不要删错:先备份
mysqldump -u root -p db_test > /backup/db_test.sql
删除前先mysqldump导出,万一删错还能恢复。生产库删除前务必先备份再操作。
注意事项
- DROP DATABASE不可回滚,DELETE/TRUNCLE都比它温柔;
- 库名区分大小写(Linux下),别拼错;
- 需要DROP权限,普通业务账号不该有这个权限;
- 删除大库会锁实例一小段时间,高峰期别执行;
- 删除库不会自动删用户授权,残留grant需手动清理;
- 删库前确认无业务连接,可先停应用再删。
总结
DROP DATABASE删除整库,加IF EXISTS避免报错中断,删前用SHOW DATABASES确认目标、mysqldump先备份,删后再SHOW DATABASES验证。这是高危不可回滚操作,业务账号不应有DROP权限,删大库避开高峰期,删库后记得清理残留的用户授权。
PHP获取文件扩展名
PHP获取文件扩展名
引言
文件上传、附件处理、图片裁剪时都要先拿到后缀判断类型。别用explode切字符串硬切,pathinfo是PHP专门解析路径的函数,能干净返回扩展名、文件名、目录名各部分。
pathinfo 标准用法
$file = '/var/www/upload/photo.2024.jpg';
$info = pathinfo($file);
echo $info['extension']; // jpg
echo $info['filename']; // photo.2024
echo $info['dirname']; // /var/www/upload
一次返回路径各组成部分,最稳妥。
只取扩展名
$ext = pathinfo($file, PATHINFO_EXTENSION);
// jpg
传第二个常量直接拿扩展名,省一次数组取值。
处理无扩展名文件
$ext = pathinfo('README', PATHINFO_EXTENSION);
// 空字符串,不会报错
没扩展名时返回空串,记得做空判断。
大写转小写统一
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
用户传JPG、Jpg、jpg都归一,上传校验用统一小写比较。
不要用explode硬切
// 反面例子:photo.2024.jpg 会切错
$parts = explode('.', $file);
$ext = end($parts);
多级点号文件名会取错,pathinfo按最后一个点正确取后缀。
注意事项
- 扩展名不等于真实MIME类型,校验文件内容要用finfo;
- 扩展名可伪造,上传安全不能只信后缀;
- PATHINFO_EXTENSION对".htaccess"这种点开头文件返回空;
- 中文文件名不受影响,pathinfo按字节正确处理;
- 取到扩展名后trim并strtolower再做白名单校验;
- 别把扩展名当安全依据,配合大小限制和重命名存储。
总结
pathinfo($file, PATHINFO_EXTENSION)取扩展名最稳,filename拿文件名、dirname拿目录,无扩展名返回空串记得判断,业务里统一strtolower小写。不要用explode切多级点号文件名。扩展名只是展示用,真实类型靠finfo判断,上传安全还需白名单加文件内容校验和重命名存储。