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'];
}

总结

记住:

  1. file_get_contents不支持https,是openssl扩展没装
  2. 装openssl扩展,重启php-fpm
  3. 或者关SSL验证临时解决
  4. 最推荐:改用curl,比file_get_contents好用多了

推荐方案:

直接用curl,别用file_get_contents搞https。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:34 

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 熟练了,团队协作效率提升很多。


emer 发布于  2026-10-9 07:34 

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 命令熟练了,开发部署效率提升很多。


emer 发布于  2026-10-9 07:33 

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);
// &lt;script&gt;alert(1)&lt;/script&gt;

十、常见坑

坑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个:

  1. strlen() 字符串长度(字节)
  2. mb_strlen() 字符长度(中文友好)
  3. substr() 截取字符串
  4. strpos() 查找位置
  5. str_replace() 替换
  6. explode() 字符串转数组
  7. implode() 数组转字符串
  8. trim() 去空格
  9. strtoupper() / strtolower() 大小写
  10. str_pad() 填充

记住:

  • 中文用mb_*系列函数
  • 查找用=== false判断
  • 数组字符串互转用explode/implode

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:33 

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性能优化的核心,一定要多实践。


emer 发布于  2026-10-9 07:32 

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个:

  1. count() 数组长度
  2. in_array() 值是否在数组里
  3. array_key_exists() key是否存在
  4. array_unique() 去重
  5. array_merge() 合并
  6. array_slice() 取一段
  7. array_sum() 求和
  8. explode() 字符串转数组
  9. implode() 数组转字符串
  10. array_column() 取二维数组某一列

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:31 

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 基本功,一定要熟练掌握。


emer 发布于  2026-10-9 07:30 

PHP cURL请求超时,超时时间设置

前言

PHP用cURL请求接口,结果一直卡住或者超时?cURL默认没有超时时间,对方服务器慢的话能卡你几分钟。必须设超时,不然整个脚本都堵死了。本文讲怎么设。

一、为什么要设超时

不设超时的后果:

  1. 对方服务器挂了,你的脚本一直等着
  2. 对方服务器慢,你的脚本卡很久
  3. 高并发下,进程都堵在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分钟总超时

总结

记住:

  1. cURL一定要设超时,不然卡死
  2. CURLOPT_CONNECTTIMEOUT 连接超时(秒)
  3. CURLOPT_TIMEOUT 总超时(秒)
  4. 单位是秒,不是毫秒
  5. 超时了用 curl_errno 判断是不是28

最常用:

curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:30 

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 高是好事
  • 内存不够先找大进程
  • 清缓存要谨慎
  • 最好加物理内存

学会看内存,服务器问题就好排查了。


emer 发布于  2026-10-9 07:29 

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',

总结

记住:

  1. PHP默认UTC时区,差8小时
  2. 改php.ini里的 date.timezone = Asia/Shanghai
  3. 改完重启php-fpm
  4. 或者代码里 date_default_timezone_set('Asia/Shanghai')
  5. CLI和FPM可能用不同的php.ini

最常用:

date_default_timezone_set('Asia/Shanghai');
echo date('Y-m-d H:i:s');

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:28 

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 生产必备配置,一定要重视。


emer 发布于  2026-10-9 07:28 

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 协助处理


emer 发布于  2026-10-9 07:27 

PHP session不生效,cookie配置检查

前言

PHP用session存用户登录状态,结果刷新页面就掉了?session不生效太常见了,一般就是cookie配置不对、路径不对、或者目录没权限。本文讲怎么排查和解决。

一、session工作原理

简单说:

  1. 浏览器访问网站
  2. PHP创建session,生成session_id
  3. session_id通过cookie发给浏览器
  4. 浏览器每次请求带上这个cookie
  5. 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

九、快速排查清单

  1. F12看cookie里有没有PHPSESSID
  2. 没有 → 查cookie_path、cookie_domain、cookie_secure
  3. 有 → 查session目录权限
  4. 都没问题 → 看session.auto_start有没有冲突
  5. 跨域问题 → cookie_domain设成 .主域名

总结

记住:

  1. session靠cookie传session_id
  2. 先看cookie里有没有PHPSESSID
  3. 没有就查cookie配置
  4. 有就查session目录权限
  5. 跨域名记得设cookie_domain=.主域名

最常用:

ini_set('session.cookie_path', '/');
session_start();

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:27 

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开发,安全永远第一位。


emer 发布于  2026-10-9 07:26 

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 协助处理


emer 发布于  2026-10-9 07:26 

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不连续是正常现象

主键设计对表性能影响很大,一定要重视。


emer 发布于  2026-10-9 07:25 

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 分析日志很方便

做好日志配置,排查问题和分析流量都有用。


emer 发布于  2026-10-9 07:24 

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得天数
  • 注意时区设置

做开发时间日期天天用,这些函数必须熟。


emer 发布于  2026-10-9 07:23 

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 按大小排序

运维排查磁盘问题,这些命令天天用。


emer 发布于  2026-10-9 07:21 

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
  • 不要自己写随机算法

做用户系统,生成密码和验证码是必备功能。


emer 发布于  2026-10-9 07:20