PHP file_get_contents获取https内容失败
前言
PHP用file_get_contents获取http内容没问题,换成https就报错了?这是最常见的问题,一般就是openssl扩展没开,或者SSL证书验证没过。本文讲怎么解决。
一、问题现象
<?php
$url = 'https://api.example.com/data';
$result = file_get_contents($url);
echo $result;
结果报错:
Warning: file_get_contents(): failed to open stream: Unable to find the wrapper "https"
或者:
Warning: file_get_contents(): SSL operation failed with code 1
二、常见原因
原因1:openssl扩展没装
PHP默认不开openssl扩展,file_get_contents不支持https。
原因2:allow_url_open没开
php.ini里禁了远程打开。
原因3:SSL证书验证失败
对方网站证书有问题,PHP验证不通过。
原因4:网络问题
服务器出不去,或者DNS解析不了。
三、排查步骤
第一步:看openssl扩展装了没
php -m | grep openssl
有输出就是装了,没输出就是没装。
或者写个phpinfo:
<?php
phpinfo();
搜"openssl",看有没有。
第二步:看allow_url_open开了没
<?php
echo ini_get('allow_url_fopen');
输出1就是开了,空就是没开。
第三步:用curl测试
curl https://api.example.com/data
curl能通就是PHP的问题,curl也不通就是网络问题。
四、解决方法
方法1:装openssl扩展(推荐)
CentOS
yum install php-openssl
或者:
yum install php7x-openssl
(x是你的PHP版本)
Ubuntu
apt install php-openssl
装完重启
systemctl restart php-fpm
方法2:改php.ini配置
打开php.ini,找到:
;extension=openssl
去掉前面的分号:
extension=openssl
然后重启php-fpm。
方法3:关掉SSL验证(临时)
不想改配置,可以在代码里关验证:
<?php
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false
]
]);
$result = file_get_contents('https://api.example.com/data', false, $context);
echo $result;
注意: 这是临时方案,生产环境建议把证书配好。
方法4:改用curl(推荐)
file_get_contents不好用,直接用curl:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo '错误: ' . curl_error($ch);
}
curl_close($ch);
echo $result;
curl比file_get_contents好用多了,推荐用curl。
五、常见坑
坑1:CLI和FPM配置不一样
命令行里openssl装了,网页上还是不行。
原因: CLI和FPM用不同的php.ini。
解决: 看phpinfo里的配置路径,改对应的那个。
坑2:装了扩展没重启
装完忘了重启php-fpm。
解决:
systemctl restart php-fpm
坑3:对方网站证书过期
对方网站SSL证书过期了,PHP验证不通过。
解决: 关验证,或者让对方更新证书。
坑4:allow_url_fopen没开
php.ini里禁了远程打开。
解决:
allow_url_fopen = On
坑5:防火墙挡了出站
服务器防火墙挡了443端口出站。
解决:
# 看能不能出去
curl -I https://www.baidu.com
# 不行的话开防火墙
firewall-cmd --add-port=443/tcp --permanent
firewall-cmd --reload
六、POST请求示例
file_get_contents发POST:
<?php
$url = 'https://api.example.com/submit';
$data = http_build_query([
'name' => '张三',
'age' => 20
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'content' => $data,
'timeout' => 30
],
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false
]
]);
$result = file_get_contents($url, false, $context);
echo $result;
还是建议用curl,更灵活。
七、封装函数
function http_get($url, $timeout = 30)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$result = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return ['code' => -1, 'msg' => $error];
}
curl_close($ch);
return ['code' => 0, 'data' => $result];
}
// 使用
$res = http_get('https://api.example.com/data');
if ($res['code'] == 0) {
echo $res['data'];
} else {
echo '请求失败: ' . $res['msg'];
}
总结
记住:
- file_get_contents不支持https,是openssl扩展没装
- 装openssl扩展,重启php-fpm
- 或者关SSL验证临时解决
- 最推荐:改用curl,比file_get_contents好用多了
推荐方案:
直接用curl,别用file_get_contents搞https。
遇到问题加QQ23979811 协助处理
Git 常用命令大全
Git 常用命令大全
Git 是开发必备工具,常用命令一定要熟练。
1. 初始化仓库
新建仓库:
git init
克隆远程仓库:
git clone https://github.com/user/repo.git
克隆指定分支:
git clone -b dev https://github.com/user/repo.git
2. 查看状态
查看状态:
git status
查看提交历史:
git log
简洁历史:
git log --oneline --graph
查看文件修改:
git diff
3. 提交代码
添加所有修改:
git add .
添加指定文件:
git add index.php
提交:
git commit -m "修复bug"
添加并提交:
git commit -am "修复bug"
4. 远程操作
查看远程仓库:
git remote -v
拉取远程更新:
git pull
推送代码:
git push
推送并设置上游分支:
git push -u origin dev
5. 分支操作
查看本地分支:
git branch
查看所有分支:
git branch -a
创建分支:
git branch dev
切换分支:
git checkout dev
创建并切换:
git checkout -b dev
合并分支:
# 先切换到master
git checkout master
# 合并dev分支
git merge dev
删除分支:
git branch -d dev
强制删除:
git branch -D dev
6. 撤销操作
撤销工作区修改:
git checkout -- index.php
撤销暂存区:
git reset HEAD index.php
回退到上个版本:
git reset --hard HEAD^
回退到指定版本:
git reset --hard commit_id
7. 标签操作
打标签:
git tag v1.0.0
查看标签:
git tag
推送标签:
git push origin v1.0.0
推送所有标签:
git push origin --tags
8. 暂存操作
暂存当前修改:
git stash
查看暂存列表:
git stash list
恢复暂存:
git stash pop
删除暂存:
git stash drop
9. 其他常用
配置用户名:
git config --global user.name "张三"
配置邮箱:
git config --global user.email "zhangsan@example.com"
查看配置:
git config --list
查看当前分支:
git branch --show-current
10. 常用工作流
开发新功能:
# 拉最新代码
git pull
# 创建功能分支
git checkout -b feature/user
# 开发完提交
git add .
git commit -m "添加用户功能"
# 推送
git push -u origin feature/user
总结
Git 常用命令要点:
- git init 初始化仓库
- git clone 克隆仓库
- git status 看状态
- git add 添加修改
- git commit 提交
- git push 推送
- git pull 拉取
- git branch 分支操作
- git checkout 切换分支
- git merge 合并分支
- git reset 回退
- git stash 暂存
Git 熟练了,团队协作效率提升很多。
Docker 常用命令大全
Docker 常用命令大全
Docker 现在开发必备,常用命令一定要熟练。
1. 镜像命令
查看本地镜像:
docker images
搜索镜像:
docker search nginx
拉取镜像:
docker pull nginx:latest
删除镜像:
docker rmi nginx:latest
删除所有镜像:
docker rmi $(docker images -q)
构建镜像:
# 在Dockerfile目录下
docker build -t myapp:1.0 .
2. 容器命令
查看运行中的容器:
docker ps
查看所有容器(包括停止的):
docker ps -a
启动容器:
docker run -d --name mynginx -p 80:80 nginx
参数说明:
- -d:后台运行
- --name:容器名
- -p:端口映射(主机:容器)
- -v:目录挂载
停止容器:
docker stop mynginx
启动已停止的容器:
docker start mynginx
重启容器:
docker restart mynginx
删除容器:
docker rm mynginx
强制删除运行中的容器:
docker rm -f mynginx
删除所有停止的容器:
docker container prune
3. 进入容器
进入容器命令行:
docker exec -it mynginx /bin/bash
退出容器:
exit
4. 日志查看
查看容器日志:
docker logs mynginx
实时查看日志:
docker logs -f mynginx
查看最近100行日志:
docker logs --tail 100 mynginx
5. 文件操作
从主机复制到容器:
docker cp index.html mynginx:/usr/share/nginx/html/
从容器复制到主机:
docker cp mynginx:/etc/nginx/nginx.conf ./
6. 容器信息
查看容器详情:
docker inspect mynginx
查看容器端口映射:
docker port mynginx
查看容器资源使用:
docker stats
7. 数据卷
创建数据卷:
docker volume create mydata
查看数据卷:
docker volume ls
使用数据卷:
docker run -d -v mydata:/data nginx
8. 网络
查看网络:
docker network ls
创建网络:
docker network create mynet
容器加入网络:
docker network connect mynet mynginx
9. 其他常用
查看Docker版本:
docker version
查看Docker信息:
docker info
清理无用资源:
docker system prune
10. 常用组合
启动一个PHP环境:
docker run -d --name php \
-p 8080:80 \
-v /www:/var/www/html \
php:apache
启动MySQL:
docker run -d --name mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-v /data/mysql:/var/lib/mysql \
mysql:5.7
启动Redis:
docker run -d --name redis \
-p 6379:6379 \
redis:latest
总结
Docker 常用命令要点:
- docker images 看镜像
- docker ps 看容器
- docker run 启动容器
- docker exec 进入容器
- docker logs 看日志
- docker cp 复制文件
- docker stats 看资源
- -d 后台运行
- -p 端口映射
- -v 目录挂载
Docker 命令熟练了,开发部署效率提升很多。
PHP字符串处理常用函数,substr、explode
前言
PHP字符串处理天天用,截取、拼接、查找、替换,每个项目都离不开。本文整理最常用的字符串函数,收藏随用随查。
一、字符串长度
strlen()
字节长度(中文算3个字节):
echo strlen('hello'); // 5
echo strlen('你好'); // 6(UTF-8每个中文3字节)
mb_strlen()
字符长度(中文算1个字符):
echo mb_strlen('你好', 'utf-8'); // 2
区别: strlen看字节,mb_strlen看字符。
二、截取字符串
substr()
$str = 'hello world';
// 从第0位取5个
echo substr($str, 0, 5); // hello
// 从第6位取到最后
echo substr($str, 6); // world
// 从倒数第5位取
echo substr($str, -5); // world
mb_substr()(中文截取)
$str = '我是中国人';
// 从第0个字符取2个
echo mb_substr($str, 0, 2, 'utf-8'); // 我是
注意: 中文一定要用mb_substr,不然会乱码。
三、查找字符串
strpos()
找位置(从左找,找到返回位置,没找到返回false):
$str = 'hello world';
echo strpos($str, 'world'); // 6
// 没找到
var_dump(strpos($str, 'abc')); // bool(false)
strrpos()
从右找:
echo strrpos('a/b/c', '/'); // 3
strstr()
找并截取:
$str = 'user@example.com';
echo strstr($str, '@'); // @example.com
str_contains()(PHP8)
var_dump(str_contains('hello world', 'world')); // bool(true)
注意: PHP8以上才有。
四、替换字符串
str_replace()
$str = 'hello world';
echo str_replace('world', 'php', $str); // hello php
str_ireplace()
不区分大小写:
echo str_ireplace('WORLD', 'php', $str); // hello php
preg_replace()
正则替换:
$str = '2026-10-09';
// 把横线换成点
echo preg_replace('/-/g', '.', $str); // 2026.10.09
五、拼接和拆分
implode()(数组转字符串)
$arr = ['a', 'b', 'c'];
echo implode(',', $arr); // a,b,c
echo implode('-', $arr); // a-b-c
explode()(字符串转数组)
$str = 'a,b,c';
$arr = explode(',', $str);
// ['a', 'b', 'c']
限制拆分次数
$str = 'a,b,c,d';
// 只拆成2段
$arr = explode(',', $str, 2);
// ['a', 'b,c,d']
六、去除空白
trim()
去首尾空格:
$str = ' hello ';
echo trim($str); // hello
ltrim() / rtrim()
ltrim($str); // 去左边
rtrim($str); // 去右边
七、大小写转换
strtoupper('hello'); // HELLO
strtolower('HELLO'); // hello
ucwords('hello world'); // Hello World
ucfirst('hello'); // Hello
八、重复和填充
str_repeat()
echo str_repeat('ab', 3); // ababab
str_pad()
// 补到10位,右边补*
echo str_pad('123', 10, '*', STR_PAD_RIGHT); // 123*******
// 左边补
echo str_pad('123', 10, '0', STR_PAD_LEFT); // 0000000123
九、常见场景
场景1:手机号中间打星号
$phone = '13812345678';
echo substr($phone, 0, 3) . '****' . substr($phone, -4);
// 138****5678
场景2:截取文件名后缀
$file = 'photo.jpg';
echo strrchr($file, '.'); // .jpg
场景3:邮箱拆分
$email = 'user@example.com';
$name = strstr($email, '@', true); // user
$domain = strstr($email, '@'); // @example.com
场景4:URL参数解析
$url = 'https://example.com/index.php?id=1&name=zhangsan';
// 拿问号后面的
$query = parse_url($url, PHP_URL_QUERY);
// id=1&name=zhangsan
parse_str($query, $params);
// ['id' => '1', 'name' => 'zhangsan']
场景5:HTML标签转义
$str = '<script>alert(1)</script>';
echo htmlspecialchars($str);
// <script>alert(1)</script>
十、常见坑
坑1:中文截取乱码
用substr截中文,结果乱码了。
原因: substr按字节截,中文一个字3个字节,容易截到半个字。
解决: 用mb_substr:
mb_substr($str, 0, 10, 'utf-8');
坑2:strpos===0的问题
$str = 'abc';
if (strpos($str, 'a') == false) {
echo '没找到';
}
问题: strpos找到a在位置0,0==false是true。
解决:
if (strpos($str, 'a') === false) {
echo '没找到';
}
注意: 一定要用===,不是==。
坑3:trim只去空格
trim默认只去空格和换行。
要去指定字符:
trim($str, "/"); // 去首尾的/
坑4:explode空字符串
$str = '';
$arr = explode(',', $str);
// [''] 不是空数组!
解决:
if ($str === '') {
$arr = [];
} else {
$arr = explode(',', $str);
}
总结
最常用的10个:
strlen()字符串长度(字节)mb_strlen()字符长度(中文友好)substr()截取字符串strpos()查找位置str_replace()替换explode()字符串转数组implode()数组转字符串trim()去空格strtoupper()/strtolower()大小写str_pad()填充
记住:
- 中文用mb_*系列函数
- 查找用=== false判断
- 数组字符串互转用explode/implode
遇到问题加QQ23979811 协助处理
MySQL 索引优化技巧
MySQL 索引优化技巧
索引是提升查询性能的关键,但用不好反而拖慢速度。
1. 为什么要建索引
- 没有索引要全表扫描
- 数据量大时查询很慢
- 索引相当于书的目录
- 合适的索引能提升百倍速度
2. 索引类型
主键索引:
- 主键自动创建
- 唯一且非空
普通索引:
- 最基本的索引
- 加速查询
唯一索引:
- 值不能重复
- 比如手机号
联合索引:
- 多个字段组成
- 遵循最左前缀原则
3. 建索引原则
1)最频繁查询的字段建索引
-- 经常按name查
CREATE INDEX idx_name ON users(name);
2)区分度高的字段建索引
- 性别这种区分度低的不要建
- 手机号、身份证这种适合
3)联合索引注意顺序
-- 联合索引 (a, b, c)
-- 最左前缀:
-- a 能用
-- a,b 能用
-- a,b,c 能用
-- 单独b,c 用不了
4. 索引失效情况
1)where 里对索引字段用函数
-- 失效
SELECT * FROM users WHERE DATE(create_time) = '2026-01-01';
-- 有效
SELECT * FROM users WHERE create_time >= '2026-01-01' AND create_time < '2026-01-02';
2)where 里对索引字段做运算
-- 失效
SELECT * FROM users WHERE age + 1 = 20;
-- 有效
SELECT * FROM users WHERE age = 19;
3)like 以 % 开头
-- 失效
SELECT * FROM users WHERE name LIKE '%张';
-- 有效
SELECT * FROM users WHERE name LIKE '张%';
4)隐式类型转换
-- phone是字符串
-- 失效(数字转字符串)
SELECT * FROM users WHERE phone = 13800138000;
-- 有效
SELECT * FROM users WHERE phone = '13800138000';
5. EXPLAIN 查看执行计划
EXPLAIN SELECT * FROM users WHERE name = '张三';
重点看:
- type: 访问类型,至少要 range
- key: 实际用到的索引
- rows: 扫描行数
- Extra: 额外信息
6. 索引不要建太多
- 每个索引都占空间
- 插入更新时要维护索引
- 不是越多越好
- 一张表索引建议不超过5个
7. 覆盖索引
避免回表:
-- 索引包含查询的所有字段
SELECT name, age FROM users WHERE name = '张三';
-- 索引 (name, age) 就是覆盖索引
-- 不用回表查主键
Extra 显示 Using index 就是覆盖索引。
8. 慢查询定位
-- 开启慢查询
slow_query_log = 1
long_query_time = 1 -- 超过1秒记录
-- 查看慢查询日志
-- 找到慢SQL,加索引优化
9. 常见优化场景
1)分页查询优化
-- 慢(深分页)
SELECT * FROM orders LIMIT 100000, 10;
-- 快(子查询)
SELECT * FROM orders
WHERE id >= (SELECT id FROM orders LIMIT 100000, 1)
LIMIT 10;
2)JOIN 字段建索引
- JOIN 的两边都要建索引
- 类型要一致
3)ORDER BY 用索引
- 排序字段建索引
- 避免 filesort
10. 索引维护
查看索引:
SHOW INDEX FROM users;
删除多余索引:
DROP INDEX idx_name ON users;
分析表:
ANALYZE TABLE users;
总结
MySQL 索引优化要点:
- 区分度高的字段建索引
- 联合索引遵循最左前缀
- 避免索引失效(函数、运算、like%)
- 用 EXPLAIN 分析执行计划
- 索引不要建太多
- 覆盖索引避免回表
- 慢查询日志找问题SQL
- 深分页要优化
- JOIN 两边都建索引
- 定期维护索引
索引优化是MySQL性能优化的核心,一定要多实践。
PHP数组常用函数大全,array操作
前言
PHP数组用得最多,几乎每个脚本都要操作数组。本文整理最常用的数组函数,收藏起来随用随查。
一、创建和遍历
数组定义
<?php
// 索引数组
$arr = [1, 2, 3, 4, 5];
// 关联数组
$user = [
'name' => '张三',
'age' => 20,
'email' => 'zhangsan@qq.com'
];
遍历
// foreach遍历
foreach ($arr as $value) {
echo $value;
}
// 带key
foreach ($user as $key => $value) {
echo "$key: $value";
}
二、增删改查
添加元素
// 末尾加
$arr[] = 6;
array_push($arr, 7, 8);
// 开头加
array_unshift($arr, 0);
// 关联数组加
$user['phone'] = '13800138000';
删除元素
// 删最后一个
array_pop($arr);
// 删第一个
array_shift($arr);
// 删指定key
unset($user['age']);
// 删多个
unset($arr[0], $arr[1]);
修改元素
$arr[0] = 100;
$user['name'] = '李四';
查找元素
// 值是否在数组里
in_array(3, $arr); // true
// 找key
array_key_exists('name', $user); // true
// 找值的key
array_search(2, $arr); // 1
三、数组统计
长度
count($arr);
sizeof($arr); // 和count一样
求和、最大、最小
array_sum($arr); // 求和
max($arr); // 最大
min($arr); // 最小
去重
$unique = array_unique($arr);
打乱
shuffle($arr);
四、排序
索引数组排序
sort($arr); // 升序
rsort($arr); // 降序
关联数组排序
asort($user); // 按值升序
arsort($user); // 按值降序
ksort($user); // 按key升序
krsort($user); // 按key降序
五、拆分和合并
取一段
// 从第2个开始取3个
$slice = array_slice($arr, 1, 3);
合并数组
$arr1 = [1, 2];
$arr2 = [3, 4];
$merge = array_merge($arr1, $arr2); // [1,2,3,4]
交差
$arr1 = [1, 2, 3];
$arr2 = [2, 3, 4];
array_intersect($arr1, $arr2); // [2,3] 交集
array_diff($arr1, $arr2); // [1] 差集
六、key和value操作
取所有key
$keys = array_keys($user);
// ['name', 'age', 'email']
取所有value
$values = array_values($user);
// ['张三', 20, 'zhangsan@qq.com']
翻转key和value
$flipped = array_flip($user);
填充数组
// 5个元素,值都是0
$arr = array_fill(0, 5, 0);
七、数组和字符串互转
数组转字符串
$arr = ['a', 'b', 'c'];
$str = implode(',', $arr); // 'a,b,c'
字符串转数组
$str = 'a,b,c';
$arr = explode(',', $str); // ['a', 'b', 'c']
八、常用场景
场景1:数组去重
$arr = [1, 2, 2, 3, 3, 3];
$unique = array_unique($arr);
// [1, 2, 3]
场景2:查值在不在
$fruits = ['apple', 'banana', 'orange'];
if (in_array('apple', $fruits)) {
echo '有苹果';
}
场景3:二维数组按key排序
$users = [
['name' => '张三', 'age' => 20],
['name' => '李四', 'age' => 25],
['name' => '王五', 'age' => 18]
];
// 按age排序
array_multisort(array_column($users, 'age'), SORT_ASC, $users);
场景4:数组分页
$page = 1;
$pageSize = 10;
$offset = ($page - 1) * $pageSize;
$pageData = array_slice($allData, $offset, $pageSize);
场景5:过滤空值
$arr = ['a', '', null, 'b', 0, 'c'];
$filtered = array_filter($arr);
// ['a', 'b', 'c'] 空字符串、null、0都被过滤了
九、常见坑
坑1:count(null)报错
PHP7.2以后,count(null)会警告。
解决:
$count = is_array($arr) ? count($arr) : 0;
坑2:==和===的区别
in_array('123', [123]); // true(==)
in_array('123', [123], true); // false(===)
注意: 第三个参数true才是严格比较。
坑3:array_merge和+的区别
$arr1 = ['a' => 1, 'b' => 2];
$arr2 = ['b' => 3, 'c' => 4];
array_merge($arr1, $arr2); // ['a'=>1, 'b'=>3, 'c'=>4]
$arr1 + $arr2; // ['a'=>1, 'b'=>2, 'c'=>4]
区别: array_merge后者覆盖前者,+前者保留。
坑4:引用传递
foreach ($arr as &$value) {
$value = $value * 2;
}
unset($value); // 一定要unset,不然后面会出问题
总结
最常用的10个:
count()数组长度in_array()值是否在数组里array_key_exists()key是否存在array_unique()去重array_merge()合并array_slice()取一段array_sum()求和explode()字符串转数组implode()数组转字符串array_column()取二维数组某一列
遇到问题加QQ23979811 协助处理
Nginx 反向代理配置
Nginx 反向代理配置
反向代理是 Nginx 最常用的功能之一,配置很简单。
1. 什么是反向代理
反向代理就是:用户访问 Nginx,Nginx 把请求转发给后端服务器,再把结果返回给用户。
作用:
- 隐藏后端服务器
- 负载均衡
- 统一入口
- 缓存静态内容
2. 基本配置
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;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
说明:
- proxy_pass:后端地址
- proxy_set_header:传递请求头
3. 常用请求头
location / {
proxy_pass http://127.0.0.1:8080;
# 传递域名
proxy_set_header Host $host;
# 传递真实IP
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;
}
4. 超时配置
location / {
proxy_pass http://127.0.0.1:8080;
# 连接超时
proxy_connect_timeout 60s;
# 发送超时
proxy_send_timeout 60s;
# 读取超时
proxy_read_timeout 60s;
}
5. WebSocket 代理
location /ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# WebSocket 升级
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
6. 负载均衡
# 定义后端服务器组
upstream backend {
server 127.0.0.1:8080 weight=1;
server 127.0.0.1:8081 weight=2;
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}
7. 静态文件直接返回
server {
listen 80;
server_name example.com;
# 静态文件直接处理
location ~* \.(js|css|png|jpg|gif)$ {
root /var/www/static;
expires 30d;
}
# 其他请求转发后端
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
8. 配置生效
# 检查配置
nginx -t
# 重载配置
nginx -s reload
9. 常见问题
1)502 Bad Gateway
- 后端服务没启动
- 后端端口不对
- 后端超时
2)504 Gateway Timeout
- 后端处理太慢
- 调整超时时间
3)获取不到真实IP
- 没配置 X-Real-IP
- 后端没读取请求头
4)WebSocket 连不上
- 没配置 Upgrade 和 Connection 头
10. 生产建议
1)一定要传请求头
- Host
- X-Real-IP
- X-Forwarded-For
2)合理设置超时
- 根据业务调整
- 不要太长也不要太短
3)静态文件 Nginx 直接处理
- 减轻后端压力
- 提高性能
4)开启 gzip 压缩
gzip on;
gzip_types text/css application/javascript;
总结
Nginx 反向代理配置要点:
- proxy_pass 指定后端地址
- 一定要传请求头
- 常用超时配置
- WebSocket 要升级头
- upstream 做负载均衡
- 静态文件直接返回
- 配置完 nginx -t 检查
- 502 检查后端是否启动
- 生产环境传全请求头
反向代理是 Nginx 基本功,一定要熟练掌握。
PHP cURL请求超时,超时时间设置
前言
PHP用cURL请求接口,结果一直卡住或者超时?cURL默认没有超时时间,对方服务器慢的话能卡你几分钟。必须设超时,不然整个脚本都堵死了。本文讲怎么设。
一、为什么要设超时
不设超时的后果:
- 对方服务器挂了,你的脚本一直等着
- 对方服务器慢,你的脚本卡很久
- 高并发下,进程都堵在cURL上,服务器卡死
一定要设!
二、两个超时参数
1. CURLOPT_CONNECTTIMEOUT
连接超时,单位秒。
就是TCP连接花多久。
推荐: 5-10秒
2. CURLOPT_TIMEOUT
总超时,单位秒。
整个请求(连接+发送+接收)花多久。
推荐: 30-60秒
区别
| 参数 | 含义 | 推荐值 |
|---|---|---|
| CURLOPT_CONNECTTIMEOUT | 连接超时 | 5秒 |
| CURLOPT_TIMEOUT | 总超时 | 30秒 |
三、完整示例
<?php
$url = 'https://api.example.com/data';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 连接超时(秒)
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
// 总超时(秒)
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
// 毫秒级超时(可选)
// curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 5000);
// curl_setopt($ch, CURLOPT_TIMEOUT_MS, 30000);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'cURL错误: ' . curl_error($ch);
}
curl_close($ch);
echo $result;
四、超时错误码
常见错误码
| 错误码 | 说明 |
|---|---|
| 28 | 超时(CURLE_OPERATION_TIMEDOUT) |
| 7 | 连接失败(CURLE_COULDNT_CONNECT) |
| 6 | 域名解析失败(CURLE_COULDNT_RESOLVE_HOST) |
检查错误码
if (curl_errno($ch) == 28) {
echo "请求超时了";
}
五、常见坑
坑1:timeout设成了毫秒
CURLOPT_TIMEOUT的单位是秒,不是毫秒!
错误:
curl_setopt($ch, CURLOPT_TIMEOUT, 5000); // 5000秒,不是5秒!
正确:
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5秒
或者用毫秒版:
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); // 5000毫秒 = 5秒
坑2:set_time_limit不够
PHP脚本本身有最大执行时间。
如果cURL超时30秒,但PHP脚本只允许10秒,PHP先超时了。
解决:
set_time_limit(0); // 不限制
// 或者
set_time_limit(60); // 60秒
坑3:DNS解析慢
域名解析也要时间,算在连接超时里。
解决: 连接超时设长一点,比如10秒。
坑4:https证书问题
https请求如果证书有问题,也会卡住或报错。
临时关闭验证:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
注意: 生产环境不建议关,最好把证书配好。
坑5:重定向太多
对方返回301/302重定向,cURL默认不自动跳。
开启自动跳转:
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3); // 最多跳3次
六、POST请求示例
<?php
$url = 'https://api.example.com/submit';
$data = [
'name' => '张三',
'age' => 20
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo '错误: ' . curl_error($ch);
}
curl_close($ch);
echo $result;
七、封装成函数
function http_curl($url, $post = false, $data = [], $timeout = 30)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if ($post) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data);
}
$result = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return ['code' => -1, 'msg' => $error];
}
curl_close($ch);
return ['code' => 0, 'data' => $result];
}
// 使用
$res = http_curl('https://api.example.com/data');
if ($res['code'] == 0) {
echo $res['data'];
} else {
echo '请求失败: ' . $res['msg'];
}
八、推荐配置
普通接口请求
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5秒连接
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 30秒总超时
快速接口(查个数据)
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // 3秒连接
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10秒总超时
慢接口(上传大文件)
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 10秒连接
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 2分钟总超时
总结
记住:
- cURL一定要设超时,不然卡死
CURLOPT_CONNECTTIMEOUT连接超时(秒)CURLOPT_TIMEOUT总超时(秒)- 单位是秒,不是毫秒
- 超时了用
curl_errno判断是不是28
最常用:
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
遇到问题加QQ23979811 协助处理
Linux 查看内存使用
Linux 查看内存使用
服务器内存不够用的时候,得知道谁在吃内存。
1. free 命令
最常用的查看内存命令:
free -h
输出说明:
- total: 总内存
- used: 已用内存
- free: 空闲内存
- shared: 共享内存
- buff/cache: 缓存
- available: 可用内存
注意: 不要只看 free,要看 available。Linux 会用空闲内存做缓存,这是好事。
2. top 命令
实时查看内存占用:
top
常用操作:
- 按
M按内存排序 - 按
P按CPU排序 - 按
q退出
重点看:
- %MEM:进程内存占用百分比
- RES:进程实际占用物理内存
3. ps 命令
查看进程内存占用:
# 按内存占用排序,前10
ps aux --sort=-%mem | head -10
输出说明:
- USER:进程用户
- %MEM:内存占用
- RSS:实际物理内存(KB)
- COMMAND:进程命令
4. vmstat 命令
查看整体内存和CPU:
# 每秒刷新一次
vmstat 1
重点列:
- swpd:交换分区使用
- free:空闲内存
- buff:缓冲区
- cache:缓存
- si:换入内存
- so:换出内存
注意: si 和 so 大于0说明内存不够,在用swap。
5. /proc/meminfo
查看详细内存信息:
cat /proc/meminfo
重点看:
- MemTotal:总内存
- MemFree:空闲内存
- MemAvailable:可用内存
- Buffers:缓冲区
- Cached:缓存
- SwapTotal:交换分区总量
- SwapFree:交换分区空闲
6. 缓存说明
Linux 内存缓存机制:
- 空闲内存用来做文件缓存
- 程序需要时会自动释放
- cached 高不是坏事
- available 才是真正可用的
7. 内存不够怎么办
1)找出吃内存的进程
ps aux --sort=-%mem | head
2)重启大内存进程
- 确认是哪个进程
- 重启释放内存
3)清理缓存
# 清理页面缓存
echo 1 > /proc/sys/vm/drop_caches
# 清理目录项和inode
echo 2 > /proc/sys/vm/drop_caches
# 全部清理
echo 3 > /proc/sys/vm/drop_caches
4)加交换分区
- 临时方案,治标不治本
- 最好加物理内存
8. 常用组合命令
# 查看内存占用前5的进程
ps aux --sort=-%mem | head -6
# 查看swap使用最多的进程
for i in $(ls /proc | grep -E '^[0-9]+'); do
awk '/VmSwap/{print $2/1024 " MB", $0}' /proc/$i/status 2>/dev/null
done | sort -nr | head
9. 监控建议
1)设置内存告警
- 超过80%告警
- 及时处理
2)定期检查
- 每天看一次内存
- 发现异常及时处理
3)不要盲目清缓存
- 缓存是好事
- 清缓存会降低性能
总结
Linux 查看内存使用要点:
- free -h 看整体内存
- top 按M看内存占用
- ps aux 排序找大进程
- vmstat 看swap使用
- /proc/meminfo 看详细信息
- 不要只看 free,要看 available
- cached 高是好事
- 内存不够先找大进程
- 清缓存要谨慎
- 最好加物理内存
学会看内存,服务器问题就好排查了。
PHP获取当前时间不对,时区设置date.timezone
前言
PHP用date()获取当前时间,结果比实际时间差了8个小时?这是最常见的时区问题。PHP默认用UTC时区,北京时间是东八区,所以差了8小时。本文讲怎么改。
一、问题现象
<?php
echo date('Y-m-d H:i:s');
输出:
2026-10-09 00:30:00
但实际北京时间是:
2026-10-09 08:30:00
差了8个小时。
二、原因
PHP默认时区是UTC(世界标准时间),不是北京时间。
北京时间是 UTC+8,所以差了8小时。
三、解决方法
方法1:改php.ini(推荐)
第一步:找到php.ini
php --ini
找到Loaded Configuration File那一行:
Loaded Configuration File: /etc/php.ini
第二步:修改date.timezone
打开php.ini,找到这行:
;date.timezone =
改成:
date.timezone = Asia/Shanghai
第三步:重启php-fpm
systemctl restart php-fpm
方法2:代码里设置(临时)
不想改php.ini,可以在代码里设:
<?php
date_default_timezone_set('Asia/Shanghai');
echo date('Y-m-d H:i:s');
注意: 要在所有日期函数之前设。
方法3:.htaccess里设
Apache的话,可以在.htaccess里设:
php_value date.timezone Asia/Shanghai
四、常用时区对照
| 时区 | 说明 |
|---|---|
| Asia/Shanghai | 北京时间(东八区) |
| Asia/Chongqing | 重庆时间 |
| Asia/Urumqi | 乌鲁木齐时间 |
| Asia/Hong_Kong | 香港时间 |
| Asia/Taipei | 台北时间 |
| Asia/Tokyo | 东京时间(东九区) |
| America/New_York | 纽约时间 |
| Europe/London | 伦敦时间 |
| UTC | 世界标准时间 |
国内用: Asia/Shanghai
五、验证时区
看当前时区
<?php
echo date_default_timezone_get();
// 输出:Asia/Shanghai
看服务器时间
date
看PHP时间
<?php
echo date('Y-m-d H:i:s');
对比一下,应该一样了。
六、常见坑
坑1:CLI和FPM时区不一样
命令行里改了php.ini,但网页上还是不对。
原因: CLI和FPM用不同的php.ini。
解决: 看phpinfo()里的配置路径,改对应的那个。
<?php
phpinfo();
搜"Loaded Configuration File",看用的哪个文件。
坑2:改了没重启
改了php.ini忘了重启php-fpm。
解决:
systemctl restart php-fpm
坑3:代码里又改回去了
某个框架或库里又调了 date_default_timezone_set('UTC')。
解决: 全局搜一下,看哪里改的。
坑4:用了gmdate()
gmdate()总是用UTC时区,不管你设什么。
比如:
echo gmdate('Y-m-d H:i:s');
这个输出的是UTC时间,不是北京时间。
解决: 用date()不要用gmdate(),或者自己加8小时。
坑5:数据库时间不对
PHP时区对了,但MySQL里的时间不对。
原因: MySQL自己也有时区设置。
解决: MySQL连接后设时区:
SET time_zone = '+8:00';
或者改my.cnf:
default-time-zone = '+8:00'
七、常用日期函数
获取当前时间
date_default_timezone_set('Asia/Shanghai');
// 当前日期时间
echo date('Y-m-d H:i:s');
// 今天
echo date('Y-m-d');
// 现在几点
echo date('H:i:s');
// 时间戳
echo time();
时间戳转日期
$time = time();
echo date('Y-m-d H:i:s', $time);
日期转时间戳
$str = '2026-10-09 08:30:00';
echo strtotime($str);
几天以后
// 3天后
echo date('Y-m-d', strtotime('+3 days'));
// 下周
echo date('Y-m-d', strtotime('next week'));
八、框架里的设置
ThinkPHP
在config/app.php里:
'default_timezone' => 'Asia/Shanghai',
Laravel
在config/app.php里:
'timezone' => 'Asia/Shanghai',
总结
记住:
- PHP默认UTC时区,差8小时
- 改php.ini里的
date.timezone = Asia/Shanghai - 改完重启php-fpm
- 或者代码里
date_default_timezone_set('Asia/Shanghai') - CLI和FPM可能用不同的php.ini
最常用:
date_default_timezone_set('Asia/Shanghai');
echo date('Y-m-d H:i:s');
遇到问题加QQ23979811 协助处理
Redis 持久化机制
Redis 持久化机制
Redis 是内存数据库,断电数据就没了,所以需要持久化。
1. 为什么要持久化
- Redis 数据在内存中
- 服务器断电/重启数据丢失
- 持久化把数据存到磁盘
- 重启后可以恢复数据
2. RDB 快照
原理: 定时把内存数据全量保存到磁盘。
优点:
- 恢复速度快
- 文件小
- 适合备份
缺点:
- 可能丢失最后一次快照后的数据
- fork子进程可能阻塞
配置:
# redis.conf
save 900 1 # 900秒内1个key变化就保存
save 300 10 # 300秒内10个key变化
save 60 10000 # 60秒内10000个key变化
手动触发:
# 前台保存(阻塞)
redis-cli save
# 后台保存(不阻塞)
redis-cli bgsave
3. AOF 日志
原理: 记录每个写命令,重启时重放。
优点:
- 数据更安全(最多丢1秒)
- 日志可读
缺点:
- 文件比 RDB 大
- 恢复速度慢
配置:
# redis.conf
appendonly yes # 开启AOF
appendfilename "appendonly.aof"
# 同步策略
appendfsync everysec # 每秒同步(推荐)
# appendfsync always # 每次写都同步
# appendfsync no # 交给操作系统
AOF 重写:
# 手动触发AOF重写
redis-cli bgrewriteaof
4. RDB vs AOF 对比
| 对比项 | RDB | AOF |
|---|---|---|
| 数据安全 | 可能丢数据 | 更安全 |
| 文件大小 | 小 | 大 |
| 恢复速度 | 快 | 慢 |
| 性能影响 | fork阻塞 | 较小 |
| 适用场景 | 备份/容灾 | 数据安全要求高 |
5. 混合持久化
Redis 4.0+ 支持混合持久化:
- AOF 重写时前半段用 RDB 格式
- 后半段增量用 AOF 格式
- 结合两者优点
配置:
aof-use-rdb-preamble yes
6. 生产建议
1)数据不敏感用 RDB
- 缓存场景
- 丢点数据没关系
2)数据重要用 AOF
- 每秒同步
- 最多丢1秒数据
3)生产环境推荐
- 同时开 RDB + AOF
- 混合持久化
- 定期备份 RDB 文件
7. 数据恢复
RDB 恢复:
# 把 dump.rdb 放到数据目录
# 重启 Redis 自动加载
AOF 恢复:
# 开启 appendonly yes
# 重启自动重放AOF日志
8. 常见问题
1)fork 阻塞怎么办
- 数据量大 fork 慢
- 用 AOF 代替
- 减少单实例数据量
2)AOF 文件太大
- 定期触发重写
- 设置自动重写阈值
3)恢复慢怎么办
- 用 RDB 恢复快
- 混合持久化更优
总结
Redis 持久化要点:
- RDB 是快照,恢复快但可能丢数据
- AOF 是日志,数据安全但文件大
- 生产建议同时开 RDB + AOF
- 混合持久化结合两者优点
- 缓存用 RDB,重要数据用 AOF
- 定期备份持久化文件
- fork 阻塞要关注数据量
持久化是 Redis 生产必备配置,一定要重视。
MySQL大表分页越查越慢,优化方案
数据量小的时候分页很快,一旦表数据上百万条,翻到后面的页就慢得要死?这是MySQL分页最常见的问题,下面说几个实用的优化方案。
1. 先看为什么会慢
SELECT * FROM 表名 ORDER BY id LIMIT 100000, 10;
这个SQL的意思是:扫描前100010条,丢掉前100000条,返回后10条。
数据量越大,扫描的行数越多,就越慢。这就是为什么越往后翻越慢。
2. 方案一:子查询优化(最常用)
SELECT * FROM 表名
WHERE id >= (SELECT id FROM 表名 ORDER BY id LIMIT 100000, 1)
LIMIT 10;
原理:
- 子查询先通过索引找到第100001条的id
- 然后直接从这个id开始往后查
- 不需要扫描前10万条
速度对比:
- 原SQL:扫描10万+行,耗时几秒
- 优化后:走索引,几毫秒就出来
3. 方案二:游标分页(推荐用在后台)
-- 第一页
SELECT * FROM 表名 ORDER BY id LIMIT 10;
-- 第二页(记住上一页最后一条的id)
SELECT * FROM 表名 WHERE id > 100 ORDER BY id LIMIT 10;
-- 第三页
SELECT * FROM 表名 WHERE id > 110 ORDER BY id LIMIT 10;
优点:
- 永远都是走索引,不管翻到第几页都一样快
- 适合无限滚动加载
缺点:
- 不能直接跳到第100页
- 不适合传统的"上一页/下一页"翻页
4. 方案三:延迟关联
SELECT t.* FROM 表名 t
INNER JOIN (
SELECT id FROM 表名 ORDER BY id LIMIT 100000, 10
) tmp ON t.id = tmp.id;
原理:
- 先通过索引查出需要的10个id
- 再用这10个id去关联原表查完整数据
- 避免回表太多行
5. 方案四:限制最大翻页深度
如果不是必须让用户翻到很后面,可以直接限制:
-- 最多只允许翻100页(1000条数据)
IF 页码 > 100 THEN
-- 提示用户"请搜索更精确的条件"
END IF;
一般用户也不会翻到很后面的页,限制一下性能就好很多。
6. 方案五:用ES做搜索
如果是文章、商品之类的内容,直接用Elasticsearch做全文搜索和分页,MySQL只存原始数据。
ES天生适合做大数据量的分页搜索,MySQL不是干这个的。
常见坑
坑1:ORDER BY 字段没加索引
ORDER BY 的字段一定要加索引,不然不管怎么优化都是全表扫描。
坑2:OFFSET 太大了
OFFSET 100万,不管怎么优化都慢,直接用游标分页。
*坑3:SELECT 不要用**
只查需要的字段,减少回表数据量。
遇到问题加QQ23979811 协助处理
PHP session不生效,cookie配置检查
前言
PHP用session存用户登录状态,结果刷新页面就掉了?session不生效太常见了,一般就是cookie配置不对、路径不对、或者目录没权限。本文讲怎么排查和解决。
一、session工作原理
简单说:
- 浏览器访问网站
- PHP创建session,生成session_id
- session_id通过cookie发给浏览器
- 浏览器每次请求带上这个cookie
- PHP根据cookie里的session_id找到对应数据
核心: session靠cookie传session_id,cookie不行session就没了。
二、常见原因
原因1:cookie没设置对
session.cookie_path、session.cookie_domain配置不对。
原因2:session保存目录没权限
session文件存不进去。
原因3:session.auto_start没开
或者开了但和代码冲突。
原因4:跨域问题
主域和子域之间cookie不共享。
原因5:HTTPS下cookie的secure问题
cookie设了secure,但用http访问。
三、排查步骤
第一步:检查session有没有启动
<?php
session_start();
// 存个值
$_SESSION['test'] = 'hello';
// 看有没有
echo '<pre>';
print_r($_SESSION);
echo '</pre>';
刷新页面,看test还在不在。
第二步:看cookie里有没有PHPSESSID
按F12打开浏览器开发者工具 → Application → Cookies。
看有没有 PHPSESSID 这个cookie。
没有的话: 就是cookie没设对。
有的话: 那就是session保存目录的问题。
四、php.ini配置检查
找到php.ini位置
php --ini
关键配置
;session保存目录
session.save_path = "/var/lib/php/session"
;是否自动启动
session.auto_start = 0
;cookie路径
session.cookie_path = /
;cookie域名
session.cookie_domain =
;cookie有效期(秒)
session.cookie_lifetime = 0
;是否仅https
session.cookie_secure = 0
;是否仅http(防XSS)
session.cookie_httponly = 1
五、常见坑和解决
坑1:session目录没权限
session文件写不进去。
检查:
ls -ld /var/lib/php/session
解决:
chmod 777 /var/lib/php/session
chown apache:apache /var/lib/php/session
或者看php-fpm用的哪个用户。
坑2:cookie_path不对
比如网站装在子目录,但cookie_path设成了别的。
解决:
ini_set('session.cookie_path', '/');
session_start();
坑3:cookie_domain不对
多域名访问时,cookie只在一个域名下有效。
解决:
ini_set('session.cookie_domain', '.example.com');
注意前面有个点,表示所有子域名都生效。
坑4:HTTPS下设了secure
cookie_secure=1表示只在https下传cookie,用http访问就没了。
解决:
session.cookie_secure = 0
或者全站用https。
坑5:session.auto_start冲突
代码里已经session_start()了,但php.ini里也开了auto_start,冲突。
解决:
session.auto_start = 0
六、代码里动态设置
不想改php.ini,可以在代码里设:
<?php
// 必须在session_start()之前
ini_set('session.cookie_path', '/');
ini_set('session.cookie_domain', '.example.com');
ini_set('session.cookie_lifetime', 0);
session_start();
$_SESSION['user_id'] = 1;
七、检查session是否生效
写个测试页:
<?php
session_start();
// 第一次访问,设置
if (!isset($_SESSION['count'])) {
$_SESSION['count'] = 1;
} else {
$_SESSION['count']++;
}
echo "访问次数: " . $_SESSION['count'];
echo "<br>";
echo "Session ID: " . session_id();
echo "<br>";
echo "Session 文件: " . session_save_path() . "/sess_" . session_id();
刷新页面,数字应该一直涨。
如果不涨: 就是session没存住。
八、常见场景
场景1:登录后刷新就掉
原因: cookie_path或domain不对。
解决: 检查cookie配置。
场景2:换了个子域名就掉
比如 www.example.com 登录了,跳到 bbs.example.com 就没登录。
原因: cookie_domain没设成 .example.com。
解决:
ini_set('session.cookie_domain', '.example.com');
场景3:从http跳到https就掉
原因: cookie_secure=1,但http不带cookie。
解决: 全站用https,或者关掉secure。
场景4:session目录满了
原因: session文件太多。
解决: 清理旧session,或者设gc_maxlifetime。
session.gc_maxlifetime = 1440
九、快速排查清单
- F12看cookie里有没有PHPSESSID
- 没有 → 查cookie_path、cookie_domain、cookie_secure
- 有 → 查session目录权限
- 都没问题 → 看session.auto_start有没有冲突
- 跨域问题 → cookie_domain设成 .主域名
总结
记住:
- session靠cookie传session_id
- 先看cookie里有没有PHPSESSID
- 没有就查cookie配置
- 有就查session目录权限
- 跨域名记得设cookie_domain=.主域名
最常用:
ini_set('session.cookie_path', '/');
session_start();
遇到问题加QQ23979811 协助处理
PHP 过滤和验证数据
PHP 过滤和验证数据
用户输入不可信,必须过滤验证,这是Web开发的基本功。
1. 为什么要过滤验证
- 防止 SQL 注入
- 防止 XSS 攻击
- 保证数据格式正确
- 防止脏数据入库
2. filter_var 函数
PHP 内置过滤器,很好用。
验证邮箱
$email = 'test@example.com';
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo '邮箱格式正确';
} else {
echo '邮箱格式错误';
}
验证数字
$age = '20abc';
if (filter_var($age, FILTER_VALIDATE_INT)) {
echo '是整数';
}
验证URL
$url = 'https://example.com';
if (filter_var($url, FILTER_VALIDATE_URL)) {
echo 'URL格式正确';
}
验证IP
$ip = '192.168.1.1';
if (filter_var($ip, FILTER_VALIDATE_IP)) {
echo 'IP格式正确';
}
3. filter_input 获取并过滤
从 GET/POST 获取并过滤:
// 获取并过滤GET参数
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
// 获取并过滤POST参数
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
4. 过滤输入
过滤HTML标签
$str = '<script>alert(1)</script>你好';
// 去除HTML标签
echo strip_tags($str);
// HTML转义(安全输出)
echo htmlspecialchars($str);
过滤特殊字符
$str = "hello ' world";
// 转义单引号
echo addslashes($str);
5. 常用验证场景
验证手机号
function checkPhone($phone) {
return preg_match('/^1[3-9]\d{9}$/', $phone);
}
if (checkPhone('13800138000')) {
echo '手机号正确';
}
验证身份证
function checkIdCard($idcard) {
return preg_match('/^\d{17}[\dXx]$/', $idcard);
}
验证用户名
// 4-16位,字母数字下划线
function checkUsername($name) {
return preg_match('/^\w{4,16}$/', $name);
}
6. 安全建议
1)所有外部输入都要验证
- GET、POST、COOKIE
- 不要相信任何用户输入
2)用预处理防SQL注入
- 不要自己拼接SQL
- 用 PDO 预处理
3)输出时转义
- 输出到HTML要 htmlspecialchars
- 防止 XSS 攻击
4)白名单验证
- 只接受允许的值
- 不要用黑名单
7. 完整示例
// 接收用户注册信息
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$age = (int)$_POST['age'];
$errors = [];
// 验证用户名
if (!preg_match('/^\w{4,16}$/', $username)) {
$errors[] = '用户名4-16位字母数字下划线';
}
// 验证邮箱
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = '邮箱格式错误';
}
// 验证年龄
if ($age < 18 || $age > 100) {
$errors[] = '年龄必须18-100岁';
}
if (empty($errors)) {
// 验证通过,入库
echo '注册成功';
} else {
print_r($errors);
}
总结
PHP 过滤和验证数据要点:
- 所有用户输入都不可信
- 用 filter_var 验证格式
- 用 filter_input 获取并过滤
- 输出用 htmlspecialchars 防XSS
- 用预处理防SQL注入
- 用正则验证手机号、用户名
- 白名单验证比黑名单好
- 不要自己拼接SQL字符串
做Web开发,安全永远第一位。
MySQL数据库导入sql文件报错解决
导入sql文件的时候各种报错?下面整理了最常见的导入报错和解决办法,照着排查就行。
1. 先看最常见的导入命令
mysql -u root -p 数据库名 < /path/to/你的文件.sql
或者进入MySQL后用source:
mysql -u root -p
USE 数据库名;
source /path/to/你的文件.sql;
2. 报错1:Unknown database
原因: 数据库不存在,你直接导入了。
解决: 先创建数据库再导入:
CREATE DATABASE 数据库名 DEFAULT CHARACTER SET utf8mb4;
然后再执行导入命令。
3. 报错2:Access denied
原因: 权限不够,或者密码错了。
解决: 确认用户名密码对不对,给用户授权:
GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost' IDENTIFIED BY '密码';
FLUSH PRIVILEGES;
4. 报错3:File not found
原因: 文件路径不对,或者文件没权限读。
解决:
- 用绝对路径,不要用相对路径
- 确认文件存在:
ls -l /path/to/file.sql - 给文件加读权限:
chmod 644 file.sql
5. 报错4:Duplicate entry
原因: 导入的表已经有数据了,主键冲突。
解决:
- 先清空表再导入:
TRUNCATE TABLE 表名; - 或者导入SQL文件开头加上
DROP TABLE IF EXISTS 表名;
6. 报错5:Unknown collation
原因: 字符集排序规则不一致,比如导出用的是 utf8mb4_general_ci,导入的数据库是 utf8_general_ci。
解决: 导入前设置字符集:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 导入你的SQL
SET FOREIGN_KEY_CHECKS = 1;
7. 报错6:MySQL server has gone away
原因: SQL文件太大,导入超时了。
解决: 修改MySQL配置:
# /etc/my.cnf
max_allowed_packet = 64M
wait_timeout = 300
interactive_timeout = 300
然后重启MySQL:
systemctl restart mysqld
8. 报错7:Syntax error
原因: SQL文件格式不对,或者MySQL版本不兼容。
解决:
- 导出的时候选择兼容版本
- 去掉注释行(-- 开头的)
- 用文本编辑器打开检查一下有没有乱码
9. 大文件导入更快的方法
如果SQL文件几百MB,用mysql命令行比phpMyAdmin快10倍:
mysql -u root -p 数据库名 < big_file.sql
还可以开事务加速:
SET autocommit = 0;
SET unique_checks = 0;
SET foreign_key_checks = 0;
-- 导入完成后
SET autocommit = 1;
SET unique_checks = 1;
SET foreign_key_checks = 1;
COMMIT;
遇到问题加QQ23979811 协助处理
MySQL 主键和自增
MySQL 主键和自增
主键是表的重要概念,搞懂怎么用很有必要。
1. 什么是主键
主键(Primary Key)是表中唯一标识一行记录的字段。
特点:
- 唯一性: 每行数据主键不能重复
- 非空性: 主键不能为 NULL
- 一张表只能有一个主键
2. 创建主键
建表时指定
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
自增主键(最常用)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
3. 自增主键特点
- 自动增长: 插入数据不用指定ID,自动+1
- 从1开始: 默认从1开始
- 不重复: 保证唯一
-- 插入数据不用写id
INSERT INTO users (name) VALUES ('张三');
-- id自动变成1
-- 再插入自动变成2
4. 查看自增值
-- 查看表当前自增值
SHOW TABLE STATUS LIKE 'users';
-- 查看建表语句
SHOW CREATE TABLE users;
5. 修改自增值
-- 设置下一次自增值为100
ALTER TABLE users AUTO_INCREMENT = 100;
6. 联合主键
多个字段组成主键:
CREATE TABLE user_role (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id)
);
7. 主键设计建议
1)用自增整数做主键
- INT 或 BIGINT
- 查询快,占用空间小
- 不要用 UUID 做主键
2)为什么不用字符串做主键
- 占空间大
- 查询慢
- 自增效率高
3)什么时候用联合主键
- 中间表(如用户角色关联表)
- 没有合适的单字段做主键
4)BIGINT 还是 INT
- 一般 INT 够用(约21亿)
- 数据量大用 BIGINT
8. 常见问题
1)删除数据后ID不连续
- 正常现象,自增只增不减
- 不要依赖ID连续
2)自增值跳号
- 删除了中间数据
- 插入失败也会占用ID
- 正常现象
3)为什么不用手机号做主键
- 可能会变
- 字符串查询慢
- 用自增ID更稳妥
总结
MySQL 主键和自增要点:
- 主键唯一且非空
- 一张表只能一个主键
- 自增主键最常用
- 用 INT 或 BIGINT
- 不要用 UUID 做主键
- 中间表可以用联合主键
- ID不连续是正常现象
主键设计对表性能影响很大,一定要重视。
Nginx 访问日志配置
Nginx 访问日志配置
访问日志是排查问题和分析流量的重要依据,配置好很有必要。
1. 默认日志位置
# 访问日志
/var/log/nginx/access.log
# 错误日志
/var/log/nginx/error.log
2. 日志格式
Nginx 默认有两种日志格式:
combined(默认)
log_format combined '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
common
log_format common '$remote_addr [$time_local] "$request" $status $body_bytes_sent';
3. 自定义日志格式
在 nginx.conf 的 http 块添加:
http {
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
}
4. 常用日志变量
- $remote_addr: 客户端IP
- $time_local: 本地时间
- $request: 请求行
- $status: 状态码
- $body_bytes_sent: 发送字节数
- $http_referer: 来源页面
- $http_user_agent: 浏览器信息
- $request_time: 请求总耗时
- $upstream_response_time: 后端响应时间
5. 按站点分开日志
不同站点用不同日志文件:
server {
listen 80;
server_name a.com;
access_log /var/log/nginx/a.access.log;
}
server {
listen 80;
server_name b.com;
access_log /var/log/nginx/b.access.log;
}
6. 关闭日志
静态资源不记日志:
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
access_log off;
}
7. 日志切割
Nginx 自带日志切割:
http {
# 每天切割,保留30天
access_log /var/log/nginx/access.log main;
log_not_found off;
}
也可以用 logrotate:
# 配置文件
/etc/logrotate.d/nginx
8. 分析日志
统计访问量
# 统计总请求数
wc -l /var/log/nginx/access.log
# 统计独立IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
统计状态码
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c
统计访问最多的URL
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
总结
Nginx 访问日志配置要点:
- 默认日志在 /var/log/nginx/
- combined 是默认格式
- 自定义格式添加 $request_time 等变量
- 不同站点分开日志文件
- 静态资源可以关闭日志
- 日志要定期切割
- 用 awk 分析日志很方便
做好日志配置,排查问题和分析流量都有用。
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
- 不要自己写随机算法
做用户系统,生成密码和验证码是必备功能。