PHP 时间日期处理
PHP 时间日期处理
时间日期是开发常用功能,这些函数要记住。
1. 获取当前时间
// 当前时间戳
echo time();
// 当前日期时间
echo date('Y-m-d H:i:s');
// 今天日期
echo date('Y-m-d');
2. 时间戳和日期互转
时间戳转日期
$time = time();
echo date('Y-m-d H:i:s', $time);
日期转时间戳
$date = '2024-01-01 12:00:00';
$time = strtotime($date);
echo $time;
3. 常用日期格式
// 2024-01-01
echo date('Y-m-d');
// 2024-01-01 12:00:00
echo date('Y-m-d H:i:s');
// 2024年01月01日
echo date('Y年m月d日');
// 2024/01/01
echo date('Y/m/d');
// 星期几
echo date('l'); // Sunday
4. 常用格式字符
- Y: 4位年
- m: 2位月
- d: 2位日
- H: 24小时制
- i: 分钟
- s: 秒
5. 日期加减
// 明天
echo date('Y-m-d', strtotime('+1 day'));
// 昨天
echo date('Y-m-d', strtotime('-1 day'));
// 一周后
echo date('Y-m-d', strtotime('+1 week'));
// 一个月后
echo date('Y-m-d', strtotime('+1 month'));
// 一年后
echo date('Y-m-d', strtotime('+1 year'));
6. 计算时间差
$start = strtotime('2024-01-01');
$end = strtotime('2024-01-10');
// 相差天数
$diff = ($end - $start) / 86400;
echo $diff . '天';
7. DateTime 类(推荐)
// 创建日期对象
$date = new DateTime('2024-01-01');
// 格式化输出
echo $date->format('Y-m-d H:i:s');
// 加一天
$date->add(new DateInterval('P1D'));
echo $date->format('Y-m-d');
// 计算两个日期差
$start = new DateTime('2024-01-01');
$end = new DateTime('2024-01-10');
$diff = $start->diff($end);
echo $diff->days . '天';
8. 常用场景
1)几天前
function timeAgo($time) {
$diff = time() - $time;
if ($diff < 60) return '刚刚';
if ($diff < 3600) return floor($diff/60) . '分钟前';
if ($diff < 86400) return floor($diff/3600) . '小时前';
return floor($diff/86400) . '天前';
}
2)生日计算
$birthday = '1990-01-01';
$age = floor((time() - strtotime($birthday)) / 31536000);
echo $age . '岁';
总结
PHP 时间日期处理要点:
- time() 获取时间戳
- date() 格式化日期
- strtotime() 日期转时间戳
- 用 +1 day 等做日期加减
- 推荐用 DateTime 类
- 时间差除以86400得天数
- 注意时区设置
做开发时间日期天天用,这些函数必须熟。
Linux 查看磁盘空间
Linux 查看磁盘空间
服务器磁盘满了怎么办?这些命令帮你快速排查。
1. df 查看整体磁盘
基本用法
# 查看磁盘使用情况
df
# 人性化显示(带M、G单位)
df -h
常用参数
- -h: 人性化显示大小
- -T: 显示文件系统类型
- -i: 查看 inode 使用情况
# 常用组合
df -hT
输出说明:
- Filesystem: 磁盘分区
- Size: 总大小
- Used: 已用
- Avail: 可用
- Use%: 使用率
- Mounted on: 挂载点
2. du 查看目录大小
查看当前目录大小
# 当前目录总大小
du -sh
# 查看指定目录
du -sh /var/log
查看子目录大小
# 查看当前目录下每个子目录大小
du -sh *
# 按大小排序
du -sh * | sort -rh
常用参数
- -s: 只显示总计
- -h: 人性化显示
- --max-depth=1: 只看一层
# 查看根目录下各目录大小
du -sh /* --max-depth=1
3. 查找大文件
查找大于100M的文件
find / -type f -size +100M -exec ls -lh {} \;
查找最近修改的大文件
find /var -type f -size +50M -mtime -7
4. 常见排查场景
1)磁盘满了
# 先看哪个分区满了
df -h
# 找到大目录
du -sh /* | sort -rh
2)日志占空间
# 查看日志目录大小
du -sh /var/log
# 清空大日志文件
> /var/log/large.log
3)inode满了
# 查看inode使用
df -i
5. 清理磁盘
清理日志文件
# 清空文件(不删除文件)
> /var/log/messages
# 删除旧日志
rm /var/log/*.old
清理缓存
# 清理包缓存
yum clean all
清理 Docker 没用的镜像
docker system prune -a
总结
Linux 查看磁盘空间要点:
- df -h 查看整体磁盘使用
- du -sh 查看目录大小
- find 查找大文件
- 磁盘满了先看分区,再找大目录
- 日志文件要定期清理
- 注意 inode 也可能满
- 用 sort -rh 按大小排序
运维排查磁盘问题,这些命令天天用。
PHP 生成随机密码
PHP 生成随机密码
注册、重置密码功能经常要生成随机密码,这几种方法要会。
1. 简单随机字符串
function generatePassword($length = 8) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[mt_rand(0, strlen($chars) - 1)];
}
return $password;
}
// 生成8位密码
echo generatePassword(8);
2. 带特殊字符的密码
function generateStrongPassword($length = 12) {
$lower = 'abcdefghijklmnopqrstuvwxyz';
$upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$numbers = '0123456789';
$special = '!@#$%^&*()';
$all = $lower . $upper . $numbers . $special;
$password = '';
// 确保至少包含每种字符
$password .= $lower[mt_rand(0, strlen($lower) - 1)];
$password .= $upper[mt_rand(0, strlen($upper) - 1)];
$password .= $numbers[mt_rand(0, strlen($numbers) - 1)];
$password .= $special[mt_rand(0, strlen($special) - 1)];
// 剩余随机
for ($i = 4; $i < $length; $i++) {
$password .= $all[mt_rand(0, strlen($all) - 1)];
}
// 打乱顺序
return str_shuffle($password);
}
echo generateStrongPassword(12);
3. 更安全的 random_bytes
PHP 7+ 推荐用 random_bytes,更安全:
function generateSecurePassword($length = 12) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
$max = strlen($chars) - 1;
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, $max)];
}
return $password;
}
echo generateSecurePassword(16);
4. 生成验证码
function generateCode($length = 6) {
$chars = '0123456789';
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $chars[random_int(0, 9)];
}
return $code;
}
// 6位数字验证码
echo generateCode(6);
5. 生成随机 Token
function generateToken($length = 32) {
return bin2hex(random_bytes($length / 2));
}
// 32位随机token
echo generateToken();
6. 安全建议
1)用 random_int 不用 mt_rand
- random_int 是密码安全随机数
- mt_rand 不够安全
2)密码长度
- 普通密码:8-12位
- 强密码:12位以上
- Token:32位以上
3)密码强度
- 大小写字母+数字+特殊字符
- 避免纯数字或纯字母
4)不要自己写随机算法
- 用 PHP 内置函数
- 不要用时间戳做随机种子
总结
PHP 生成随机密码要点:
- 简单密码用 mt_rand 拼接
- 强密码包含大小写+数字+特殊字符
- 安全密码用 random_int
- Token 用 bin2hex(random_bytes())
- 验证码用纯数字
- 推荐用 PHP 7+ 的 random_int
- 不要自己写随机算法
做用户系统,生成密码和验证码是必备功能。
MySQL 常用数据类型
MySQL 常用数据类型
建表选对数据类型很重要,这些常用类型要记住。
1. 数值类型
整数类型
- TINYINT: 1字节,范围 -128~127
- SMALLINT: 2字节,范围 -32768~32767
- INT: 4字节,常用,范围约 ±21亿
- BIGINT: 8字节,大数值
小数类型
- FLOAT: 单精度浮点数
- DOUBLE: 双精度浮点数
- DECIMAL: 精确小数,存金额用这个
-- 金额用 DECIMAL(总位数, 小数位数)
price DECIMAL(10,2) -- 最多10位,2位小数
2. 字符串类型
CHAR: 定长字符串,比如 CHAR(10) 存不满补空格
- 适合固定长度,比如手机号、性别
VARCHAR: 变长字符串,最常用
- VARCHAR(255),存多少占多少
- 适合变长内容,比如用户名、标题
TEXT: 长文本
- TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT
- 存文章内容、备注等
常用选择:
- 短文本:VARCHAR(50)
- 长文本:TEXT
- 固定长度:CHAR
3. 日期时间类型
DATE: 日期,格式 YYYY-MM-DD
TIME: 时间,格式 HH:MM:SS
DATETIME: 日期+时间,范围 1000-9999年
TIMESTAMP: 时间戳,范围 1970-2038年,自动更新
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
4. 常用字段设计示例
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
age TINYINT UNSIGNED COMMENT '年龄',
phone CHAR(11) COMMENT '手机号',
money DECIMAL(10,2) DEFAULT 0 COMMENT '余额',
status TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用',
content TEXT COMMENT '简介',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 选择建议
1)主键ID
- 用 INT 或 BIGINT
- 不要用 UUID 做主键
2)金额
- 用 DECIMAL,不要用 FLOAT/DOUBLE
- 避免精度丢失
3)状态字段
- 用 TINYINT,不要用 VARCHAR 存 "正常/禁用"
4)时间
- 创建时间用 DATETIME
- 更新时间用 TIMESTAMP 自动更新
5)字符串
- 短文本用 VARCHAR
- 长文本用 TEXT
- 固定长度用 CHAR
6. 注意事项
- 不要过度设计: 够用就行,不要字段太大
- 能用数字不用字符串: 排序、计算都快
- 避免 NULL: 尽量设默认值,NULL 不好查询
- 加注释: 每个字段都加 COMMENT
总结
MySQL 常用数据类型要点:
- INT/BIGINT:整数
- DECIMAL:金额
- VARCHAR:字符串
- TEXT:长文本
- DATETIME/TIMESTAMP:时间
- 主键用 INT 自增
- 金额用 DECIMAL
- 状态用 TINYINT
- 字段加注释
建表选对类型,性能和存储都好。
Docker 查看容器日志
Docker 查看容器日志
容器出问题怎么排查?先看日志,这些命令要会。
1. 查看容器日志
基本命令
# 查看全部日志
docker logs 容器名
# 查看容器ID
docker logs 容器ID
2. 常用参数
看最后几行
# 最后10行
docker logs --tail 10 容器名
# 最后20行
docker logs --tail 20 容器名
实时跟踪日志(最常用)
docker logs -f 容器名
带时间戳
docker logs -t 容器名
组合使用
# 实时跟踪 + 时间戳 + 最后10行
docker logs -f -t --tail 10 容器名
3. 按时间查看
# 最近10分钟的日志
docker logs --since 10m 容器名
# 最近1小时的日志
docker logs --since 1h 容器名
# 指定时间开始
docker logs --since "2024-01-01T00:00:00" 容器名
4. 多容器日志
docker-compose 查看日志
# 查看所有服务日志
docker-compose logs
# 实时跟踪
docker-compose logs -f
# 查看指定服务
docker-compose logs nginx
# 看最后50行
docker-compose logs --tail 50 nginx
5. 日志持久化
json-file 日志驱动(默认)
- 日志存在 /var/lib/docker/containers/
- 单个容器日志文件
配置日志大小限制
# docker-compose.yml
services:
app:
image: nginx
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
6. 常见排查场景
1)容器启动失败
docker logs 容器名
看最后几行报错信息。
2)接口报错
docker logs -f --tail 100 容器名
实时看请求日志。
3)查某个时间点的错误
docker logs --since "2024-01-01T10:00:00" 容器名 | grep "error"
7. 日志清理
清理单个容器日志
# 找到日志文件
sudo find /var/lib/docker/containers/ -name "*.log"
# 清空日志
sudo sh -c "echo > /var/lib/docker/containers/容器ID/容器ID-json.log"
重启容器自动清理
docker restart 容器名
总结
Docker 查看容器日志要点:
- docker logs 查看日志
- -f 实时跟踪最常用
- --tail 看最后几行
- -t 带时间戳
- --since 按时间查
- docker-compose logs 多容器
- 配置日志大小防止占满磁盘
- 排查问题先看日志
容器运维,看日志是第一步。
PHP PDO 连接数据库
PHP PDO 连接数据库
PDO 是 PHP 推荐的数据库连接方式,支持多种数据库,防注入。
1. 什么是 PDO
PDO(PHP Data Objects)是 PHP 数据库抽象层,统一接口操作多种数据库。
2. 为什么用 PDO
- 防SQL注入: 预处理语句
- 支持多数据库: MySQL、PostgreSQL、SQLite 等
- 面向对象: 写法更规范
- 错误处理: 统一的错误模式
3. 连接数据库
// 数据库配置
$host = '127.0.0.1';
$dbname = 'test';
$user = 'root';
$pass = 'root';
try {
// 连接数据库
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$user,
$pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
echo '连接成功';
} catch(PDOException $e) {
die('连接失败:' . $e->getMessage());
}
4. 查询数据
查询多条
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll();
查询单条
$stmt = $pdo->query("SELECT * FROM users WHERE id = 1");
$user = $stmt->fetch();
5. 预处理语句(防注入)
查询
// 问号占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([1]);
$user = $stmt->fetch();
// 命名占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute([':id' => 1]);
$user = $stmt->fetch();
插入
$stmt = $pdo->prepare("INSERT INTO users (name, age) VALUES (:name, :age)");
$stmt->execute([
':name' => '张三',
':age' => 20
]);
更新
$stmt = $pdo->prepare("UPDATE users SET name = :name WHERE id = :id");
$stmt->execute([
':name' => '李四',
':id' => 1
]);
删除
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute([':id' => 1]);
6. 获取自增ID
$pdo->exec("INSERT INTO users (name) VALUES ('王五')");
$id = $pdo->lastInsertId();
7. 事务处理
try {
$pdo->beginTransaction();
$pdo->exec("INSERT INTO orders ...");
$pdo->exec("UPDATE stock ...");
$pdo->commit();
echo '事务成功';
} catch(Exception $e) {
$pdo->rollBack();
echo '事务失败:' . $e->getMessage();
}
8. 常用配置
// 设置返回关联数组
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
// 错误模式
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
// 关闭预处理模拟(真正服务端预处理)
PDO::ATTR_EMULATE_PREPARES => false
总结
PHP PDO 连接数据库要点:
- PDO 是统一数据库接口
- 预处理语句防 SQL 注入
- 支持事务操作
- 配置错误模式方便调试
- 推荐用命名占位符
- 生产环境关闭预处理模拟
- 记得设置 utf8mb4 字符集
做数据库开发,PDO 比 mysqli 更好用。
Linux 查看系统日志
Linux 查看系统日志
排查问题先看日志,这些命令要记住。
1. 系统日志位置
/var/log/messages # 系统主日志
/var/log/syslog # Debian/Ubuntu 系统日志
/var/log/boot.log # 启动日志
/var/log/cron # 定时任务日志
/var/log/secure # 安全日志(登录、sudo等)
/var/log/nginx/ # Nginx 日志
/var/log/httpd/ # Apache 日志
/var/log/mysql/ # MySQL 日志
2. 基本查看命令
cat:查看全部
cat /var/log/messages
tail:看末尾几行(最常用)
# 看最后10行
tail /var/log/messages
# 看最后20行
tail -n 20 /var/log/messages
# 实时跟踪日志(最常用)
tail -f /var/log/messages
head:看开头几行
head -n 10 /var/log/messages
3. 搜索日志
grep:搜索关键词
# 搜索错误信息
grep "error" /var/log/messages
# 不区分大小写
grep -i "error" /var/log/messages
# 显示行号
grep -n "error" /var/log/messages
# 反向匹配(不包含error)
grep -v "debug" /var/log/messages
结合管道
# 实时日志里搜索错误
tail -f /var/log/messages | grep "error"
# 搜索最近的nginx错误
tail -n 1000 /var/log/nginx/error.log | grep "500"
4. journalctl 查看 systemd 日志
# 查看全部日志
journalctl
# 看最后100行
journalctl -n 100
# 实时跟踪
journalctl -f
# 按服务查看
journalctl -u nginx
# 按时间查看
journalctl --since "1 hour ago"
journalctl --since "2024-01-01" --until "2024-01-02"
5. 常用排查场景
1)服务器登录失败
grep "Failed password" /var/log/secure
2)服务起不来
systemctl status nginx
journalctl -u nginx -n 50
3)磁盘满了
df -h
du -sh /var/log/*
4)定时任务没执行
cat /var/log/cron
6. 日志清理
手动删除大日志
# 查看日志大小
du -sh /var/log/*
# 清空日志(不删除文件)
> /var/log/messages
# 删除旧日志
rm /var/log/messages.1
logrotate 自动切割
- Linux 自带日志切割工具
- 配置文件在 /etc/logrotate.conf
- 一般不用管,自动轮转
总结
Linux 查看日志要点:
- 日志都在 /var/log/ 目录
- tail -f 实时看日志最常用
- grep 搜索关键词
- journalctl 查 systemd 服务日志
- 排查问题先看错误日志
- 日志太大要及时清理
- 系统自动轮转不用手动管
运维排查,看日志是第一步。
MySQL 视图的使用
MySQL 视图的使用
视图是虚拟表,用好了能简化查询、保护数据。
1. 什么是视图
视图(View)是基于 SQL 查询结果的虚拟表,本身不存数据,数据来自真实表。
2. 为什么用视图
1)简化查询
- 复杂查询封装成视图
- 直接查视图就行
2)权限控制
- 只暴露部分字段给用户
- 敏感字段不暴露
3)数据独立
- 表结构变了,视图不变
- 上层应用不用改
3. 创建视图
-- 创建视图
CREATE VIEW v_user_order AS
SELECT u.id, u.name, o.order_no, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
-- 使用视图
SELECT * FROM v_user_order WHERE id = 1;
4. 修改和删除视图
-- 修改视图
CREATE OR REPLACE VIEW v_user_order AS
SELECT u.id, u.name, o.order_no, o.amount, o.create_time
FROM users u
JOIN orders o ON u.id = o.user_id;
-- 删除视图
DROP VIEW IF EXISTS v_user_order;
5. 查看视图
-- 查看所有视图
SHOW FULL TABLES WHERE Table_type = 'VIEW';
-- 查看视图结构
DESC v_user_order;
-- 查看创建语句
SHOW CREATE VIEW v_user_order;
6. 视图的限制
1)不能用 ORDER BY 在视图里
- 外层查询可以排序
- 视图里排序意义不大
2)某些视图不能更新
- 包含 GROUP BY、DISTINCT、JOIN 等的视图
- 不能通过视图 INSERT/UPDATE/DELETE
3)视图不存数据
- 每次查询都执行 SQL
- 复杂视图查询可能慢
7. 实际使用建议
1)视图不是越多越好
- 复杂业务逻辑用视图
- 简单查询直接写 SQL
2)视图不要嵌套太多层
- 视图套视图性能差
- 最多一层就够了
3)敏感数据保护
- 密码、手机号等敏感字段
- 用视图隐藏或脱敏
8. 视图和表的区别
| 对比项 | 表 | 视图 |
|---|---|---|
| 存数据 | 是 | 否(虚拟表) |
| 占空间 | 是 | 否 |
| 查询速度 | 快 | 取决于SQL |
| 安全性 | 低 | 高 |
总结
MySQL 视图使用要点:
- 视图是虚拟表,不存数据
- 简化复杂查询
- 保护敏感数据
- 用 CREATE VIEW 创建
- 简单视图可以更新
- 复杂视图查询可能慢
- 不要过度使用视图
视图是好东西,但别滥用。
PHP 上传文件处理
PHP 上传文件处理
文件上传是 Web 开发常见功能,安全处理很重要。
1. HTML 表单
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
注意:必须加 enctype="multipart/form-data"。
2. PHP 接收文件
// $_FILES 接收上传文件
$file = $_FILES['file'];
// 文件信息
$name = $file['name']; // 文件名
$tmp = $file['tmp_name']; // 临时路径
$size = $file['size']; // 文件大小
$error = $file['error']; // 错误码
$type = $file['type']; // MIME类型
3. 错误码说明
- UPLOAD_ERR_OK (0): 上传成功
- UPLOAD_ERR_INI_SIZE (1): 超过 php.ini 限制
- UPLOAD_ERR_FORM_SIZE (2): 超过表单限制
- UPLOAD_ERR_PARTIAL (3): 部分上传
- UPLOAD_ERR_NO_FILE (4): 没有文件
4. 完整上传代码
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['file'];
// 检查错误
if ($file['error'] !== UPLOAD_ERR_OK) {
exit('上传失败');
}
// 检查大小(限制2M)
if ($file['size'] > 2 * 1024 * 1024) {
exit('文件太大');
}
// 允许的扩展名
$allowExt = ['jpg', 'png', 'gif'];
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
if (!in_array($ext, $allowExt)) {
exit('不允许的文件类型');
}
// 生成新文件名
$newName = uniqid() . '.' . $ext;
$uploadDir = 'uploads/';
// 移动文件
if (move_uploaded_file($file['tmp_name'], $uploadDir . $newName)) {
echo '上传成功';
} else {
echo '保存失败';
}
}
5. php.ini 配置
; 上传最大文件大小
upload_max_filesize = 2M
; 表单最大提交大小
post_max_size = 8M
; 允许上传
file_uploads = On
; 临时目录
upload_tmp_dir = /tmp
6. 安全注意事项
1)不要用原文件名
- 防止覆盖文件和路径穿越
- 用 uniqid 生成新文件名
2)验证文件类型
- 不要只看扩展名
- 用 finfo 检查 MIME 类型
$finfo = new Finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
3)上传目录不可执行
- 上传目录关闭 PHP 执行
- 防止上传木马
4)重命名文件
- 不要保留用户上传的文件名
- 防止覆盖和目录穿越攻击
7. 常见问题
1)上传失败,$_FILES 为空
- 检查表单是否有 enctype
- 检查 post_max_size 够不够大
2)move_uploaded_file 失败
- 检查目录权限
- 检查目录是否存在
3)大文件上传超时
- 修改 max_execution_time
- 修改 memory_limit
总结
PHP 文件上传要点:
- 表单必须加 enctype
- 用 $_FILES 接收文件
- 检查错误码和文件大小
- 验证文件类型
- 重命名文件防止安全问题
- 上传目录关闭执行权限
- 注意 php.ini 配置
做上传功能,安全永远第一位。
Nginx 反向代理原理
Nginx 反向代理原理
反向代理是 Nginx 最常用的功能,搞懂原理很有必要。
1. 什么是反向代理
正向代理:代理客户端,比如翻墙工具。
反向代理:代理服务器,客户端不知道真实服务器。
用户请求 Nginx,Nginx 再转发给后端服务器,用户以为自己直接访问的就是 Nginx。
2. 为什么用反向代理
1)隐藏真实服务器
- 后端服务器 IP 不暴露
- 更安全
2)负载均衡
- 多个后端服务器分摊请求
- 提高并发能力
3)统一入口
- 多个服务用一个域名
- 不同路径转发到不同服务
4)SSL 卸载
- HTTPS 证书在 Nginx 配置
- 后端用 HTTP 就行
5)缓存静态内容
- Nginx 直接返回静态资源
- 减轻后端压力
3. 基本配置
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
4. 常用代理头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
后端通过这些头获取真实客户端信息。
5. 路径转发示例
# 根路径转发到PHP服务
location / {
proxy_pass http://127.0.0.1:8080;
}
# /api 转发到后端接口
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
# /static 直接读本地文件
location /static/ {
root /var/www;
}
6. 超时设置
# 连接超时
proxy_connect_timeout 60s;
# 读取超时
proxy_read_timeout 60s;
# 发送超时
proxy_send_timeout 60s;
7. 常见问题
1)后端获取不到真实IP
- 检查 X-Real-IP 头是否设置
- 后端代码是否读取这个头
2)WebSocket 连接失败
- 需要添加 Upgrade 头
- 配置 HTTP/1.1
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
3)大文件上传失败
- 修改 client_max_body_size
- 增加 proxy_read_timeout
总结
Nginx 反向代理要点:
- 代理服务器,隐藏真实后端
- 实现负载均衡、统一入口
- 配置 proxy_pass 转发
- 记得设置常用代理头
- 后端通过请求头获取真实客户端信息
- 注意超时和上传大小限制
- WebSocket 需要特殊配置
做 Web 开发,反向代理是必备技能。
Redis 过期策略详解
Redis 过期策略详解
Redis 设置了过期时间后,怎么删除过期的 key?这就涉及过期策略。
1. 设置过期时间
# 设置过期时间(秒)
set name "张三" ex 60
# 给已有key设置过期时间
expire name 60
# 查看剩余时间
ttl name
# 移除过期时间
persist name
2. 两种过期策略
1)惰性删除
- 等到访问这个 key 的时候才检查是否过期
- 过期了就删除,返回 nil
- 优点:对 CPU 友好
- 缺点:对内存不友好,很多过期 key 没被访问就占着内存
2)定期删除
- 每隔一段时间,随机检查一些 key
- 发现过期的就删除
- 优点:定期清理,控制内存
- 缺点:随机抽查可能漏
Redis 实际是两种策略结合用。
3. 内存淘汰策略
当内存满了,写入新数据时怎么办?
# 查看当前淘汰策略
config get maxmemory-policy
常用淘汰策略:
- noeviction: 不淘汰,写入报错(默认)
- allkeys-lru: 所有 key 中淘汰最久没用的
- allkeys-random: 所有 key 中随机淘汰
- volatile-lru: 只在设置了过期时间的 key 中淘汰最久没用的
- volatile-random: 只在设置了过期时间的 key 中随机淘汰
- volatile-ttl: 淘汰即将过期的 key
生产推荐:allkeys-lru
4. LRU 和 LFU
- LRU(最近最少使用): 淘汰最久没访问的
- LFU(最不经常使用): 淘汰访问次数最少的
Redis 4.0 开始支持 LFU。
5. 实际使用建议
-
设置合理的 maxmemory
- 不要让 Redis 把内存占满
- 一般设为服务器内存的 70%
-
选择合适的淘汰策略
- 纯缓存场景:allkeys-lru
- 有持久化数据:volatile-lru
-
key 一定要设过期时间
- 重要数据不要设过期
- 缓存数据都要设过期时间
-
监控内存使用
- 用 info memory 查看
- 关注 used_memory 指标
6. 查看内存信息
# 查看内存使用
info memory
# 查看key数量
dbsize
# 查看大key
--bigkeys
总结
Redis 过期策略要点:
- 惰性删除:访问时才删
- 定期删除:定期抽查清理
- 两种策略结合使用
- 内存满了要配置淘汰策略
- 生产推荐 allkeys-lru
- 缓存数据都要设过期时间
- 监控内存使用情况
搞懂过期策略,Redis 用起来才不踩坑。
MySQL 字符集设置
MySQL 字符集设置
中文乱码是数据库开发常见问题,字符集设置一定要搞对。
1. 常用字符集
- utf8: 最多3字节,不支持 emoji
- utf8mb4: 最多4字节,支持 emoji,推荐用这个
- gbk: 中文编码,不推荐
2. 查看当前字符集
-- 查看数据库字符集
SHOW VARIABLES LIKE 'character_set%';
-- 查看排序规则
SHOW VARIABLES LIKE 'collation%';
3. 创建数据库时指定
CREATE DATABASE mydb
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
4. 创建表时指定
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 修改已有表字符集
-- 修改表字符集
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4;
-- 修改字段字符集
ALTER TABLE users MODIFY name VARCHAR(50) CHARACTER SET utf8mb4;
6. 连接时设置字符集
PHP 连接数据库后一定要设置:
$conn = mysqli_connect('localhost', 'user', 'pass', 'db');
mysqli_set_charset($conn, 'utf8mb4');
或者 SQL 语句:
SET NAMES utf8mb4;
7. 中文乱码排查
出现中文乱码,按顺序检查:
- 数据库字符集是不是 utf8mb4
- 表字符集是不是 utf8mb4
- 字段字符集是不是 utf8mb4
- 连接字符集是不是 utf8mb4
- 前端页面编码是不是 utf-8
8. 常用排序规则
- utf8mb4_general_ci: 不区分大小写,常用
- utf8mb4_unicode_ci: 支持 Unicode,更准确
- utf8mb4_bin: 区分大小写,按二进制比较
总结
MySQL 字符集设置要点:
- 统一用 utf8mb4,不要用 utf8
- 数据库、表、字段都要设置
- 连接后要设置字符集
- 中文乱码逐层排查
- 推荐 utf8mb4_general_ci 排序
字符集统一,才能避免乱码问题。
PHP 超全局变量详解
PHP 超全局变量详解
PHP 超全局变量在所有作用域都能用,写接口和表单开发天天用到。
1. 什么是超全局变量
超全局变量是 PHP 预定义的数组变量,不需要 global 声明,在函数、类、文件里都能直接访问。
2. 常用超全局变量
1)$_GET
获取 URL 查询参数。
// 访问 index.php?id=1&name=张三
$id = $_GET['id']; // 1
$name = $_GET['name']; // 张三
2)$_POST
获取表单 POST 提交的数据。
// 表单 method="post"
$username = $_POST['username'];
$password = $_POST['password'];
3)$_REQUEST
同时获取 GET、POST、COOKIE 数据。
$id = $_REQUEST['id'];
// 不推荐,安全隐患大
4)$_SESSION
会话数据,跨页面共享。
session_start();
$_SESSION['user_id'] = 1;
$_SESSION['username'] = '张三';
5)$_COOKIE
获取客户端 Cookie。
$token = $_COOKIE['token'];
6)$_SERVER
服务器和执行环境信息。
// 获取请求方法
$method = $_SERVER['REQUEST_METHOD'];
// 获取当前URL
$uri = $_SERVER['REQUEST_URI'];
// 获取客户端IP
$ip = $_SERVER['REMOTE_ADDR'];
// 获取HTTP头
$host = $_SERVER['HTTP_HOST'];
3. 文件上传变量
$_FILES
处理文件上传。
// 表单 enctype="multipart/form-data"
$file = $_FILES['upload'];
$name = $file['name']; // 文件名
$tmp = $file['tmp_name']; // 临时路径
$size = $file['size']; // 文件大小
$error = $file['error']; // 错误码
4. 注意事项
- 安全问题: 所有超全局变量都不可信,必须做过滤和验证
- isset 判断: 取值前用 isset 判断是否存在,避免报错
- 类型转换: 数字参数记得转 int
- 不要用 $_REQUEST: 安全隐患大,明确用 GET 还是 POST
5. 安全取值示例
// 安全获取GET参数
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
// 安全获取POST参数
$username = isset($_POST['username']) ? trim($_POST['username']) : '';
// 判断请求方法
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 处理POST请求
}
总结
PHP 超全局变量要点:
- $_GET:获取 URL 参数
- $_POST:获取表单数据
- $_SESSION:会话数据
- $_COOKIE:客户端 Cookie
- $_SERVER:服务器信息
- $_FILES:文件上传
- 所有外部输入都要过滤验证,不能直接用
- 取值前先 isset 判断
做 PHP 开发,超全局变量是基础中的基础。
PHP报错Call to undefined function解决
前言
运行PHP代码,突然报错:"Call to undefined function xxx()"?这个错太常见了,一般就是扩展没装或者文件没引入。本文讲怎么排查和解决。
一、报错长啥样
Fatal error: Uncaught Error: Call to undefined function mysql_connect() in xxx.php on line 5
意思:你调用了一个不存在的函数。
二、常见原因
原因1:PHP扩展没装
比如用了 mysqli_connect(),但没装mysqli扩展。
或者用了 gd库 的函数,但没装gd。
原因2:文件没引入
你自己写的函数,忘了include/require文件。
原因3:函数名写错了
拼写错了,比如 str_lower 写成 strlower。
原因4:PHP版本不对
低版本PHP没有高版本才有的函数。
三、排查步骤
第一步:看是哪个函数报错
报错信息里会告诉你函数名:
Call to undefined function curl_init()
这里就是 curl_init() 函数没找到。
第二步:判断是内置函数还是自己写的
- 内置函数:PHP自带的,比如
curl_init、mysqli_connect - 自己写的:你在别的文件里定义的
第三步:如果是内置函数,查扩展装了没
php -m | grep curl
看看curl扩展装了没。
或者写个phpinfo:
<?php
phpinfo();
?>
搜这个函数名对应的扩展,看有没有。
四、安装扩展
例子1:装curl扩展
# CentOS
yum install php-curl
# Ubuntu
apt install php-curl
例子2:装mysqli扩展
yum install php-mysqli
例子3:装gd库
yum install php-gd
装完重启
systemctl restart php-fpm
五、如果是自己写的函数
检查文件引入
<?php
// 引入函数文件
require 'functions.php';
// 调用
my_function();
检查函数名对不对
// functions.php里定义的
function my_function() {
// ...
}
调用的时候名字要一模一样。
检查命名空间
如果用了命名空间,要注意:
<?php
namespace App;
// 调用别的命名空间的函数
\OtherNamespace\my_function();
六、常见函数和对应扩展
| 函数 | 需要的扩展 |
|---|---|
| mysqli_connect | mysqli |
| curl_init | curl |
| imagecreate | gd |
| mb_strlen | mbstring |
| openssl_encrypt | openssl |
| pdo连接 | pdo_mysql |
| json_encode | json(默认有) |
| file_get_contents | 不需要(默认有) |
七、常见坑
坑1:mysql函数被废弃了
PHP7以后,mysql_* 函数都没了,要用mysqli或者PDO。
Call to undefined function mysql_connect()
解决: 换成mysqli或者PDO。
坑2:CLI和FPM装的扩展不一样
命令行里装了,网页上没有。
原因: CLI和FPM用不同的php.ini。
解决: 看phpinfo里的配置,装到对应的PHP版本里。
坑3:装了扩展没重启
装完忘了重启php-fpm。
解决:
systemctl restart php-fpm
坑4:函数名大小写
PHP函数名不区分大小写,但自己写的类方法区分。
坑5:Composer自动加载没生效
用了Composer的库,但没 vendor/autoload.php。
require 'vendor/autoload.php';
八、快速排查
第一步:看报错的函数名
Call to undefined function xxx()
第二步:查扩展
php -m | grep 扩展名
第三步:没装就装
yum install php-扩展名
第四步:重启
systemctl restart php-fpm
九、常用扩展安装
一次性装常用扩展
yum install php-mysqli php-curl php-gd php-mbstring php-xml php-json
Ubuntu
apt install php-mysqli php-curl php-gd php-mbstring php-xml
总结
记住:
- 报错会告诉你是哪个函数没找到
- 内置函数就查扩展装了没
- 自己写的就查文件引入了没
- 装完扩展要重启php-fpm
- mysql_* 函数PHP7没了,换mysqli
排查流程:
看报错函数名 → 判断是内置还是自己写的 → 内置查扩展 → 自己写的查引入 → 解决
遇到问题加QQ23979811 协助处理
PHP mysqli连接MySQL,完整示例代码
前言
PHP连MySQL用mysqli扩展,比mysql扩展好用,支持预处理、事务等。本文写个完整的连接和增删改查示例。
一、连接数据库
面向过程写法
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '123456';
$dbname = 'test';
$port = 3306;
// 连接
$conn = mysqli_connect($host, $user, $pass, $dbname, $port);
// 检查连接
if (!$conn) {
die('连接失败: ' . mysqli_connect_error());
}
echo '连接成功';
面向对象写法
<?php
$conn = new mysqli('127.0.0.1', 'root', '123456', 'test', 3306);
if ($conn->connect_error) {
die('连接失败: ' . $conn->connect_error);
}
echo '连接成功';
二、设置字符集
mysqli_set_charset($conn, 'utf8mb4');
// 或者
$conn->set_charset('utf8mb4');
注意: 一定要设utf8mb4,不然中文表情存不进去。
三、查询数据(SELECT)
面向过程
$sql = "SELECT id, name, age FROM users WHERE status = 1";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
while ($row = mysqli_fetch_assoc($result)) {
echo "ID: " . $row['id'] . " 姓名: " . $row['name'] . "<br>";
}
} else {
echo "没有数据";
}
面向对象
$sql = "SELECT id, name, age FROM users WHERE status = 1";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
echo "ID: " . $row['id'] . " 姓名: " . $row['name'] . "<br>";
}
}
取所有数据为数组
$list = [];
while ($row = mysqli_fetch_assoc($result)) {
$list[] = $row;
}
print_r($list);
四、插入数据(INSERT)
直接写SQL
$name = '张三';
$age = 20;
$sql = "INSERT INTO users (name, age) VALUES ('$name', $age)";
if (mysqli_query($conn, $sql)) {
echo "插入成功,ID: " . mysqli_insert_id($conn);
} else {
echo "插入失败: " . mysqli_error($conn);
}
预处理(防SQL注入,推荐)
$stmt = mysqli_prepare($conn, "INSERT INTO users (name, age) VALUES (?, ?)");
mysqli_stmt_bind_param($stmt, 'si', $name, $age);
// 'si' 表示:string, int
$name = '李四';
$age = 25;
if (mysqli_stmt_execute($stmt)) {
echo "插入成功,ID: " . mysqli_insert_id($conn);
} else {
echo "失败: " . mysqli_error($conn);
}
五、更新数据(UPDATE)
$id = 1;
$name = '王五';
$sql = "UPDATE users SET name = '$name' WHERE id = $id";
if (mysqli_query($conn, $sql)) {
echo "更新成功,影响行数: " . mysqli_affected_rows($conn);
}
六、删除数据(DELETE)
$id = 1;
$sql = "DELETE FROM users WHERE id = $id";
if (mysqli_query($conn, $sql)) {
echo "删除成功";
}
七、关闭连接
mysqli_close($conn);
// 或者
$conn->close();
八、完整示例封装
<?php
class DB
{
private $conn;
public function __construct()
{
$this->conn = new mysqli('127.0.0.1', 'root', '123456', 'test', 3306);
if ($this->conn->connect_error) {
die('连接失败');
}
$this->conn->set_charset('utf8mb4');
}
// 查询多行
public function queryAll($sql)
{
$result = $this->conn->query($sql);
$list = [];
while ($row = $result->fetch_assoc()) {
$list[] = $row;
}
return $list;
}
// 查询单行
public function queryOne($sql)
{
$result = $this->conn->query($sql);
return $result->fetch_assoc();
}
// 关闭
public function __destruct()
{
$this->conn->close();
}
}
// 使用
$db = new DB();
$users = $db->queryAll("SELECT * FROM users");
print_r($users);
九、常见坑
坑1:SQL注入
直接拼接变量到SQL里,有注入风险。
解决: 用预处理语句(?占位符)。
坑2:中文乱码
忘了设字符集。
解决: 连接后立刻 set_charset('utf8mb4')。
坑3:只取一行
用 mysqli_fetch_assoc 循环多次才取完。
注意: 这个函数每次只取一行,要循环。
坑4:连接失败没报错
生产环境关了错误显示。
解决: 手动检查 mysqli_connect_error()。
坑5:忘了关连接
频繁连接不关,占资源。
解决: 用完就关,或者用单例。
总结
记住:
mysqli_connect连接数据库set_charset('utf8mb4')设字符集mysqli_query执行SQLmysqli_fetch_assoc取数据- 防注入用预处理(?占位符)
- 用完
mysqli_close关连接
最常用:
$conn = mysqli_connect('127.0.0.1', 'root', '123456', 'test', 3306);
mysqli_set_charset($conn, 'utf8mb4');
遇到问题加QQ23979811 协助处理
PHP上传文件大小限制,修改php.ini
前言
上传个大点的文件,提示"文件过大"或者直接报错?那是PHP上传限制太小了。本文讲怎么改php.ini调大上传限制。
一、默认限制多大
PHP默认上传限制一般是:
upload_max_filesize= 2Mpost_max_size= 8M
传个10M的文件就不行了。
二、要改哪几个参数
1. upload_max_filesize
单个文件最大大小:
upload_max_filesize = 100M
2. post_max_size
整个POST请求最大大小:
post_max_size = 100M
注意: post_max_size要 >= upload_max_filesize。
3. memory_limit
脚本可用内存:
memory_limit = 128M
要比post_max_size大。
4. max_execution_time
脚本最大执行时间(秒):
max_execution_time = 300
传大文件要久一点,设长点。
5. max_input_time
接收数据最大时间(秒):
max_input_time = 300
三、找php.ini
php --ini
或者写个phpinfo:
<?php
phpinfo();
?>
看Loaded Configuration File。
四、修改php.ini
改成100M上传
upload_max_filesize = 100M
post_max_size = 100M
memory_limit = 128M
max_execution_time = 300
max_input_time = 300
改成50M上传
upload_max_filesize = 50M
post_max_size = 50M
memory_limit = 64M
五、改完重启
PHP-FPM
systemctl restart php-fpm
Apache
systemctl restart httpd
六、验证
写个phpinfo,看这几个参数改了没:
<?php
phpinfo();
?>
搜 upload_max_filesize,看是不是改过来了。
七、Nginx也要改
光改PHP不够,Nginx也有限制。
Nginx配置
client_max_body_size 100M;
改完重载
nginx -t
nginx -s reload
八、常见坑
坑1:改了php.ini不生效
改的不是实际加载的那个。
解决: 用phpinfo看实际加载的是哪个。
坑2:只改了upload_max_filesize
post_max_size没改,还是不行。
注意: 两个都要改,post_max_size >= upload_max_filesize。
坑3:Nginx挡了
PHP改了,Nginx的client_max_body_size没改,还是413错误。
解决: Nginx也改。
坑4:内存不够
Allowed memory size of ... bytes exhausted
解决: 加大memory_limit。
坑5:上传超时
大文件传一半就断了。
解决: 加大max_execution_time和max_input_time。
坑6:.htaccess里覆盖了
有些主机在.htaccess里设了php_value。
检查: 看看.htaccess里有没有。
九、快速排查
上传报错
- 先看PHP的upload_max_filesize够不够
- 再看post_max_size够不够
- 再看Nginx的client_max_body_size够不够
- 再看memory_limit够不够
十、推荐配置
一般网站(20M上传)
upload_max_filesize = 20M
post_max_size = 20M
memory_limit = 64M
文件站(100M上传)
upload_max_filesize = 100M
post_max_size = 100M
memory_limit = 128M
max_execution_time = 300
max_input_time = 300
Nginx对应
client_max_body_size 100M;
总结
记住:
- 改三个参数:upload_max_filesize、post_max_size、memory_limit
- post_max_size >= upload_max_filesize
- Nginx也要改client_max_body_size
- 改完重启php-fpm和reload nginx
- 大文件要加大超时时间
最常用:
upload_max_filesize = 100M
post_max_size = 100M
memory_limit = 128M
遇到问题加QQ23979811 协助处理
PHP开启错误显示,php.ini配置修改
前言
PHP报错了,页面白屏或者500,啥也不显示,不知道哪错了?开了错误显示就能看到具体错误了。本文讲怎么改php.ini开启错误显示。
一、为什么不显示错误
生产环境为了安全,默认不显示错误,防止暴露敏感信息(路径、数据库密码等)。
但开发的时候就不方便了,啥也看不到。
二、找到php.ini
找php.ini位置
php --ini
输出:
Loaded Configuration File: /etc/php.ini
或者:
/etc/php/7.4/cli/php.ini
注意:CLI和FPM的php.ini可能不一样。
看当前加载的是哪个
写个phpinfo:
<?php
phpinfo();
?>
浏览器访问,看Loaded Configuration File。
三、修改php.ini
1. 显示错误
display_errors = On
2. 报错级别
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
或者全开:
error_reporting = E_ALL
3. 错误日志
log_errors = On
error_log = /var/log/php_errors.log
4. 显示启动错误
display_startup_errors = On
四、改完重启
PHP-FPM
systemctl restart php-fpm
或者:
systemctl reload php-fpm
Apache
systemctl restart httpd
五、代码里临时开启
不想改php.ini,在代码开头加:
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
或者:
ini_set('error_reporting', E_ALL);
ini_set('display_errors', 1);
六、常见错误类型
E_ALL
所有错误和警告。
E_ERROR
致命运行错误,脚本停止。
E_WARNING
运行时警告,脚本继续。
E_NOTICE
运行时注意,比如未定义变量。
七、常见坑
坑1:改了php.ini不生效
改的不是实际加载的那个php.ini。
解决: 用 php --ini 看实际加载的是哪个。
坑2:FPM和CLI的php.ini不一样
命令行改了,网页上不生效。
原因: CLI和FPM用不同的php.ini。
解决: 改FPM的那个,比如 /etc/php/7.4/fpm/php.ini。
坑3:页面白屏
可能是语法错误,php.ini都没加载起来。
检查: 看PHP错误日志。
坑4:开了错误显示有安全风险
生产环境不能开!
注意: 排查完一定要改回去。
坑5:.htaccess里覆盖了
有些主机在.htaccess里设了php_flag。
检查: 看看.htaccess里有没有。
八、生产环境配置
生产环境不要开display_errors,但要开日志:
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED
九、查看错误日志
PHP错误日志
tail -f /var/log/php_errors.log
Nginx错误日志
tail -f /var/log/nginx/error.log
Apache错误日志
tail -f /var/log/httpd/error_log
十、快速排查
第一步:代码里临时开
ini_set('display_errors', 1);
error_reporting(E_ALL);
第二步:看具体报错
刷新页面,看报什么错。
第三步:解决完删掉
改完了把代码里的ini_set删掉,或者改回生产配置。
总结
记住:
display_errors = On显示错误error_reporting = E_ALL显示所有错误- 改完重启php-fpm
- 生产环境关掉display_errors,开log_errors
- 代码里可以用ini_set临时开
开发环境推荐:
display_errors = On
error_reporting = E_ALL
log_errors = On
生产环境推荐:
display_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_NOTICE
遇到问题加QQ23979811 协助处理
Composer安装依赖超时,国内镜像源配置
前言
composer install 装依赖,半天没反应,最后超时失败?那是因为默认的源在国外,国内访问慢。换个国内镜像就快了。
一、为什么会超时
Composer默认从 packagist.org 下载包,服务器在国外,国内访问慢,经常超时。
换个国内镜像源,速度飞快。
二、常用国内镜像
1. 阿里云镜像
https://mirrors.aliyun.com/composer/
2. 腾讯云镜像
https://mirrors.tencent.com/composer/
3. 华为云镜像
https://mirrors.huaweicloud.com/repository/php/
4. 豆瓣镜像(老了,不推荐)
https://packagist.phpcomposer.com
三、全局配置镜像
方法1:命令行配置
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
方法2:改config文件
编辑 ~/.composer/config.json:
{
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}
}
查看当前镜像
composer config -g repo.packagist
四、项目单独配置
在项目根目录的 composer.json 里加:
{
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}
}
或者用命令:
composer config repo.packagist composer https://mirrors.aliyun.com/composer/
五、临时使用镜像
不想改全局配置,临时用一次:
composer install --prefer-dist -vvv --repository-url=https://mirrors.aliyun.com/composer/
六、其他常用配置
1. 禁用github接口调用
有时候github的API调用会卡住,禁用掉:
composer config -g github-protocols https
2. 国内composer下载地址
安装composer的时候也慢,用国内下载:
php -r "copy('https://mirrors.aliyun.com/composer/composer.phar', 'composer.phar');"
3. 查看配置
composer config -g --list
七、安装依赖
安装
composer install
更新
composer update
加-v看详细过程
composer install -vvv
八、常见坑
坑1:装了还是慢
缓存没清。
解决:
composer clear-cache
坑2:报SSL错误
SSL certificate problem
解决: 关掉SSL验证(不推荐临时用):
composer config -g secure-http false
坑3:提示文件权限不够
Failed to download ...
解决: 目录权限不对。
chmod -R 777 vendor
chmod -R 777 composer.json
坑4:内存不够
Allowed memory size of ... bytes exhausted
解决: 加大内存:
php -d memory_limit=-1 /usr/local/bin/composer install
坑5:镜像也挂了
阿里云镜像有时候也抽风。
解决: 换一个镜像,比如腾讯云的。
九、验证镜像生效
查看当前镜像源
composer config -g repo.packagist
输出应该是你设的阿里云地址。
装个小包试试速度
composer require monolog/monolog
看下载速度快不快。
十、推荐配置
一键配置阿里云镜像
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
清缓存
composer clear-cache
安装
composer install
总结
记住:
- 慢就换国内镜像,阿里云的最稳
- 全局配置:
composer config -g repo.packagist composer 地址 - 不行就清缓存:
composer clear-cache - 换镜像:阿里云、腾讯云、华为云
- 临时用:加
--repository-url=地址
最常用:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
遇到问题加QQ23979811 协助处理
ThinkPHP6 500报错,开启错误日志查看原因
前言
ThinkPHP6网站突然500报错,页面啥也不显示,不知道哪错了?本文讲怎么开启错误显示和日志,快速定位问题。
一、为什么500不显示错误
生产环境为了安全,关闭了错误显示,直接返回500,不显示具体错误信息。
这是对的,不然用户能看到你的代码路径、数据库密码等敏感信息。
但排查问题就不方便了。
二、临时开启错误显示
修改 .env 文件
项目根目录下有个 .env 文件:
APP_DEBUG = true
把 APP_DEBUG 改成 true。
刷新页面,就能看到具体错误了。
注意
排查完记得改回 false! 生产环境不能开debug。
三、开启日志记录
就算不开debug,错误也会记录到日志文件里。
日志配置
config/log.php 文件:
return [
// 默认日志通道
'default' => 'file',
// 日志通道列表
'channels' => [
'file' => [
// 日志记录方式
'type' => 'File',
// 日志保存目录
'path' => '',
// 单文件日志写入
'single' => false,
// 独立日志级别
'apart_level' => [],
// 最大日志文件数量
'max_files' => 30,
],
],
];
日志位置
默认在 runtime/log/ 目录下。
按日期分目录:
runtime/log/
├── 2026-10/
│ ├── 08.log
│ ├── 07.log
│ └── 06.log
└── 2026-09/
└── ...
查看今天的错误
tail -f runtime/log/2026-10/08.log
四、常见500错误排查
1. 目录权限问题
runtime目录没权限写:
Permission denied
解决:
chmod -R 755 runtime
chown -R www:www runtime
2. 缺少扩展
Call to undefined function xxx()
解决: 装对应的PHP扩展。
3. 数据库连接失败
SQLSTATE[HY000] [1045] Access denied
检查: 数据库配置对不对。
.env 或 config/database.php:
'hostname' => '127.0.0.1',
'database' => '数据库名',
'username' => '用户名',
'password' => '密码',
4. 路由错误
Route not found
检查: 路由配置对不对。
5. 模板找不到
template not exists
检查: 模板文件路径对不对。
五、手动写日志
代码里可以手动写日志:
// 记录错误
Trace::error('这是一个错误信息');
// 记录警告
Trace::warning('这是一个警告');
// 记录信息
Trace::info('这是一个信息');
或者用助手函数:
trace('这是调试信息', 'debug');
六、查看PHP错误日志
除了ThinkPHP的日志,PHP本身也有错误日志。
查看PHP错误日志位置
php --ini | grep error_log
Nginx错误日志
tail -f /var/log/nginx/error.log
七、常见排查步骤
第一步:开debug看具体错误
改 .env:
APP_DEBUG = true
刷新页面,看具体报什么错。
第二步:看ThinkPHP日志
tail -f runtime/log/2026-10/08.log
第三步:看Nginx错误日志
tail -f /var/log/nginx/error.log
第四步:看PHP错误日志
tail -f /var/log/php-fpm/error.log
第五步:解决完改回debug
改完 .env:
APP_DEBUG = false
八、常见坑
坑1:开了debug还是不显示
缓存没清。
解决: 删除runtime目录下的缓存:
rm -rf runtime/cache/*
坑2:日志目录没权限
stream_socket_client(): unable to connect
runtime目录没写权限。
解决:
chmod -R 755 runtime
坑3:生产环境开着debug
危险!用户能看到你的敏感信息。
解决: 排查完立刻改回 APP_DEBUG = false。
坑4:日志太大占磁盘
日志越积越多,磁盘满了。
解决: 设 max_files,自动删除旧日志:
'max_files' => 30,
坑5:502和500搞混
- 500:PHP代码错误
- 502:PHP-FPM挂了
区分: 看Nginx错误日志。
总结
记住:
- 改
.env的APP_DEBUG = true临时看错误 - 生产环境必须改回
false - 日志在
runtime/log/目录 - 排查完记得清缓存
- 500是PHP代码问题,502是FPM问题
排查流程:
开debug → 看具体报错 → 看ThinkPHP日志 → 看Nginx日志 → 解决问题 → 关debug
遇到问题加QQ23979811 协助处理
PHP FPM进程过多导致服务器卡顿优化
前言
PHP FPM进程太多,把服务器内存吃满,网站卡顿甚至502?本文讲怎么优化PHP FPM进程数。
一、为什么FPM进程多会卡
PHP FPM每处理一个请求,就开一个进程。
进程开多了:
- 内存占满
- CPU切换不过来
- 服务器卡顿
- 甚至OOM被杀
二、查看当前进程数
查看PHP FPM进程
ps aux | grep php-fpm
数一下有多少个进程。
看每个进程占多少内存
ps aux | grep php-fpm | awk '{print $6/1024 "MB"}'
算一下总内存
ps aux | grep php-fpm | awk '{sum+=$6} END {print sum/1024 "MB"}'
三、FPM配置文件在哪
CentOS
/etc/php-fpm.d/www.conf
或者:
/etc/php/7.4/fpm/pool.d/www.conf
Ubuntu
/etc/php/7.4/fpm/pool.d/www.conf
四、进程管理模式
PHP FPM有三种进程管理模式:
1. static(静态)
固定进程数,启动就开这么多。
pm = static
pm.max_children = 50
2. dynamic(动态)
默认模式,按需开进程。
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 5
pm.max_spare_servers = 35
3. ondemand
没人访问的时候不开进程,来了请求才开。
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
五、怎么算合适的进程数
公式
pm.max_children = 可用内存 / 每个进程平均内存
例子
- 服务器2G内存
- 系统和其他服务占1G
- 剩下1G给PHP
- 每个PHP进程平均占40M
pm.max_children = 1024 / 40 ≈ 25
所以设25个进程差不多。
别设太大
设太大了,内存不够用,就会OOM。
六、推荐配置
小服务器(1G内存)
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
中服务器(2-4G内存)
pm = dynamic
pm.max_children = 30
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
大服务器(8G以上)
pm = dynamic
pm.max_children = 80
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 40
七、其他优化参数
request_terminate_timeout
单个请求最长执行时间,超时就杀掉。
request_terminate_timeout = 30s
slow log
慢日志,看看哪个请求慢。
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 10s
php_admin_value[memory_limit]
每个PHP进程的内存限制。
php_admin_value[memory_limit] = 128M
八、改完配置重载
systemctl reload php-fpm
或者:
systemctl restart php-fpm
九、验证效果
看进程数
ps aux | grep php-fpm | wc -l
看内存
free -h
看负载
top
十、常见坑
坑1:max_children设太大
比如2G内存设了100个进程,内存直接爆。
解决: 按上面的公式算,别拍脑袋。
坑2:改完配置不生效
忘了重载。
解决:
systemctl reload php-fpm
坑3:进程数一直涨
有慢请求,占着进程不放。
检查:
request_terminate_timeout = 30s
设个超时,超时就杀。
坑4:502 Bad Gateway
进程都忙不过来,Nginx连不上FPM。
解决: 加max_children,或者优化慢请求。
坑5:用了static模式
static固定进程数,不用就浪费内存。
推荐: 用dynamic或ondemand。
十一、排查步骤
第一步:看是不是FPM进程多
ps aux | grep php-fpm | wc -l
第二步:看内存够不够
free -h
第三步:看有没有慢请求
tail -f /var/log/php-fpm/slow.log
第四步:调整配置
改www.conf里的pm.max_children等参数。
第五步:重载验证
systemctl reload php-fpm
总结
记住:
- 进程数别设太大,按内存算
- 公式:可用内存 / 每个进程平均内存
- 推荐用dynamic模式
- 设个request_terminate_timeout,防慢请求占着进程
- 改完记得reload
推荐配置:
pm = dynamic
pm.max_children = 30
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
request_terminate_timeout = 30s
遇到问题加QQ23979811 协助处理