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开发,安全永远第一位。
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 开发,反向代理是必备技能。
Nginx反向代理配置,把请求转发到后端服务
前言
现在做后端开发,后端服务一般跑在 3000、8080 这种端口上,总不能让用户直接访问 IP:端口 吧?这时候就需要 Nginx 反向代理,把 80 端口的请求转发到后端服务。本文就讲一下 Nginx 反向代理的配置方法。
什么是反向代理
简单说,反向代理就是:
- 用户访问 Nginx(80 端口)
- Nginx 把请求转发给后端服务(比如 3000 端口)
- 后端服务返回结果给 Nginx
- Nginx 再返回给用户
用户不知道后端服务的存在,只跟 Nginx 打交道。
基本配置
最简单的反向代理
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
就这么简单,所有访问 www.example.com 的请求都会被转发到 http://127.0.0.1:3000。
检查并重载
nginx -t
systemctl reload nginx
常用配置项
proxy_pass 后端地址
proxy_pass http://127.0.0.1:3000;
后端服务的地址,可以是 IP 加端口,也可以是域名。
传递真实客户端 IP
默认情况下,后端服务看到的是 Nginx 的 IP,不是真实用户的 IP。需要加上几个请求头:
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
这样后端服务就能拿到真实用户的 IP 了。
代理 WebSocket
如果后端服务用了 WebSocket,需要额外配置:
location /ws {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
超时设置
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
如果后端服务响应慢,可以把超时时间调大一点。
按路径分流
不同路径转发到不同后端服务:
server {
listen 80;
server_name www.example.com;
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
location /admin/ {
proxy_pass http://127.0.0.1:8080;
}
location / {
root /var/www/html;
index index.html;
}
}
这样:
/api/开头的请求 → 3000 端口/admin/开头的请求 → 8080 端口- 其他请求 → 静态文件
常见坑
坑1:502 Bad Gateway
访问报 502,说明 Nginx 连不上后端服务。
排查:
- 看后端服务有没有启动:
curl http://127.0.0.1:3000 - 看后端服务监听的地址对不对,是不是只监听了 127.0.0.1
- 看防火墙有没有挡住
坑2:后端拿不到真实 IP
日志里全是 Nginx 的 IP,不是用户真实 IP。
解决: 加上那几个请求头:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
坑3:静态资源 404
前端是 Vue/React 这种单页应用,刷新页面就 404。
原因: Nginx 找不到对应的静态文件。
解决: 加个 try_files:
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ /index.html;
}
所有找不到的路径都返回 index.html,交给前端路由处理。
坑4:HTTPS 代理后端 HTTP
前端是 HTTPS,后端是 HTTP,浏览器报混合内容错误。
解决: 确保 Nginx 和后端之间是 HTTP 没问题,但要让后端知道原始请求是 HTTPS:
proxy_set_header X-Forwarded-Proto $scheme;
总结
Nginx 反向代理的核心就是 proxy_pass,把请求转发到后端服务。常用配置:
| 配置项 | 作用 |
|---|---|
proxy_pass |
后端服务地址 |
proxy_set_header Host |
传递域名 |
proxy_set_header X-Real-IP |
传递真实 IP |
proxy_set_header X-Forwarded-For |
传递完整 IP 链 |
proxy_read_timeout |
读取超时时间 |
反向代理是 Nginx 最常用的功能之一,做 Web 开发必备。
遇到问题加QQ23979811 协助处理
PHP 性能优化:从代码到部署的完整指南
前言
PHP 性能优化是每个 PHP 开发者都绕不开的话题。很多人觉得 PHP 性能差,但实际上通过合理的优化,PHP 应用完全可以达到很高的性能水平。本文从代码层面到部署层面,带你完整梳理 PHP 性能优化的思路。
一、代码层面优化
1. 避免在循环中做数据库查询
// 慢:循环中查询数据库
foreach ($users as $user) {
$order = $db->query("SELECT * FROM orders WHERE user_id = {$user['id']}");
}
// 快:一次性查询,然后用数组映射
$userIds = array_column($users, 'id');
$orders = $db->query("SELECT * FROM orders WHERE user_id IN (" . implode(',', $userIds) . ")")->fetchAll();
$orderMap = array_column($orders, null, 'user_id');
2. 用生成器处理大数据
// 慢:一次性加载所有数据到内存
$users = $db->query("SELECT * FROM big_table")->fetchAll();
// 快:用生成器逐行处理
function getUsers() {
$stmt = $pdo->query("SELECT * FROM big_table");
while ($row = $stmt->fetch()) {
yield $row;
}
}
3. 字符串拼接 vs implode
// 慢:频繁拼接字符串
$str = '';
foreach ($items as $item) {
$str .= $item . ',';
}
// 快:用数组收集最后拼接
$parts = [];
foreach ($items as $item) {
$parts[] = $item;
}
$str = implode(',', $parts);
二、PHP 配置优化
1. OPcache 必开
OPcache 是 PHP 性能提升最大的一个扩展,没有之一。
; php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
开启 OPcache 后,PHP 脚本会被编译成字节码缓存起来,避免每次请求都重新编译。性能提升通常在 2-5 倍。
2. 合理设置内存限制
memory_limit = 256M
不要设置太大,也不要太小。根据你的应用实际情况调整。
三、数据库优化
1. 开启慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
找出慢查询,然后针对性优化。
2. 使用持久化连接要谨慎
// 不一定更快,可能反而更慢
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::ATTR_PERSISTENT => true
]);
持久化连接在高并发场景下可能导致连接耗尽,要根据实际情况测试。
四、部署层面优化
1. 使用 PHP-FPM 优化
; php-fpm/www.conf
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
根据服务器内存调整 max_children,每个 PHP-FPM 进程大约占用 20-40MB 内存。
2. Nginx 开启 gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
3. 静态文件 CDN 加速
把图片、CSS、JS 等静态资源放到 CDN 上,减轻服务器压力。
五、性能监控工具
- Xdebug:代码性能分析,找出瓶颈函数
- Blackfire:更专业的性能分析工具
- New Relic:APM 应用性能监控
- ab / wrk:压力测试工具
总结
PHP 性能优化的核心思路:
- 先找出瓶颈在哪里,不要盲目优化
- 代码层面:减少数据库查询、减少内存占用
- 配置层面:OPcache 必开
- 部署层面:PHP-FPM、Nginx、数据库都要调优
- 用工具量化优化效果
性能优化是一个持续迭代的过程,不要指望一次性优化到位。每次做一点,慢慢积累。
PHP 8.3 新特性详解:类型类获取、动态常量获取和更多实用功能
前言
PHP 8.3 于 2023 年 11 月正式发布,作为 PHP 8.x 系列的第四个版本,它带来了一系列实用的语法增强和性能优化。本文将深入介绍 PHP 8.3 中最值得关注的几个新特性。
1. 类型类获取(Typed class constants)
在 PHP 8.3 之前,类常量无法声明类型。从 PHP 8.3 开始,类常量可以指定类型,这有助于提高代码的类型安全性。
class Foo
{
const string BAR = 'bar';
const int MAX_COUNT = 100;
}
声明了类型的常量必须与声明的类型匹配,否则会抛出 TypeError。支持的类型包括:标量类型、array、object、static、self、parent、类名/接口名以及 iterable。
2. 动态类常量获取
PHP 8.3 新增了 constant() 函数的增强,允许通过表达式动态获取类常量:
class Status
{
const DRAFT = 'draft';
const PUBLISHED = 'published';
const ARCHIVED = 'archived';
}
$status = 'PUBLISHED';
echo constant(Status::class . "::$status"); // 输出: published
这在需要根据变量动态选择常量时非常有用,避免了大量的 switch 或 match 分支。
3. #[\Override] 注解
PHP 8.3 引入了 #[\Override] 注解,用于显式标记一个方法是重写父类的方法:
abstract class Controller
{
abstract public function handle(): void;
}
class UserController extends Controller
{
#[\Override]
public function handle(): void
{
echo "Handling user request...";
}
}
如果使用了 #[\Override] 注解,但方法实际上并没有重写任何父类方法(比如方法名拼错了),PHP 会在编译时报错。这在大型项目中非常有价值,可以防止因为方法名不一致导致的隐蔽 bug。
4. Readonly 深拷贝
PHP 8.3 改进了 readonly 属性的处理。在 PHP 8.2 中,readonly 属性不能通过克隆来修改。PHP 8.3 允许在克隆时修改 readonly 属性:
class Point
{
public function __construct(
public readonly int $x,
public readonly int $y
) {}
}
$p1 = new Point(10, 20);
$p2 = clone $p1;
$p2->x = 30; // PHP 8.3 中允许,PHP 8.2 会报错
这个特性在值对象模式中特别有用,可以创建修改后的副本而不影响原始对象。
5. 其他实用改进
json_validate() 函数
PHP 8.3 新增了 json_validate() 函数,用于验证 JSON 字符串是否有效,而不需要解码:
$json = '{"name": "Alice", "age": 30}';
if (json_validate($json)) {
echo "Valid JSON";
}
这比 json_decode() 更高效,因为它不需要分配内存来存储解码后的结果。
随机浮点数
PHP 8.3 的 Random\Randomizer 类新增了 getFloat() 方法:
$r = new Random\Randomizer();
$float = $r->getFloat(0, 10, \Random\IntervalBoundary::ClosedOpen);
总结
PHP 8.3 虽然不是一个大版本更新,但它带来的每一个新特性都直击开发者的日常痛点。类型类常量和 #[\Override] 注解增强了代码的可维护性,动态常量获取简化了代码逻辑,readonly 深拷贝则让值对象模式更加实用。
建议正在使用 PHP 8.x 的项目尽快升级到 8.3,既能获得这些新特性的好处,也能享受性能上的小幅提升。