PHP cURL发送POST
PHP cURL发送POST
引言
后端对接第三方接口、登录模拟、提交表单,PHP最常用cURL扩展发HTTP请求。file_get_contents只能简单GET,cURL能控制POST、请求头、Cookie、超时和错误处理,是对接API的标配。
基础POST表单
$ch = curl_init('https://api.example.com/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'username' => 'user01',
'password' => '123456',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 结果返回字符串而非直接输出
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$res = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
POSTFIELDS传数组即multipart表单,接口收到的是form-data。
发送JSON
对接REST接口要手动编码并声明头:
$json = json_encode(['name' => '测试', 'age' => 18]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
]);
注意JSON方式POSTFIELDS传字符串,别传数组。
带Cookie维持登录
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookie.txt'); // 第一次保存
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt'); // 第二次读取
模拟登录再访问需鉴权页面时,用cookie文件维持会话。
错误与状态码处理
if ($err) { echo '请求失败:' . $err; }
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data = json_decode($res, true);
别只看返回体,必须判断$code和curl_error,500/404也要处理。
注意事项
- 关SSL校验仅限本地测试,生产保留CURLOPT_SSL_VERIFYPEER;
- 超时必须设,防止接口慢拖垮进程;
- HTTPS证书异常先排查证书链,别长期关闭校验;
- 敏感token放请求头,别写进URL参数;
- 用完curl_close释放句柄。
总结
cURL POST先init,设CURLOPT_POST和POSTFIELDS,表单传数组、JSON传字符串并带头,RETURNTRANSFER取回结果,配合超时、错误判断和状态码,生产保留SSL校验。掌握这套就能稳定对接绝大多数第三方API。
Redis缓存穿透击穿雪崩区别与防护方案
前言
Redis 把热点数据放进内存提速,但缓存一旦失效或被恶意穿透,数据库会被瞬间打垮。很多人把穿透、击穿、雪崩混为一谈,三者成因不同,防护手段也不一样,本篇一次讲清区别和方案。
一、缓存穿透:查不存在的数据
攻击者或程序反复查询一个数据库里根本不存在的 key,缓存永远不命中,每次都打到 MySQL。
- 典型:刷不存在的用户ID、商品ID。
- 防护:
- 布隆过滤器(Bloom Filter):请求先过过滤器,不存在直接拦截,不查库;
- 缓存空值:对查不到的结果也缓存一个空对象并设短过期时间(如60秒)。
// 伪代码:缓存空值
$user = $db->find($id);
if ($user === null) {
$redis->setex("u:$id", 60, 'NULL');
return null;
}
$redis->setex("u:$id", 3600, json_encode($user));
二、缓存击穿:热点key过期瞬间
一个被高频访问的热点 key 突然过期,大量并发同时打到数据库重建缓存。和穿透区别:数据真实存在,只是刚好过期。
- 防护:
- 热点key逻辑过期,不设物理TTL,后台异步刷新;
- 加互斥锁(SETNX),只让一个线程回源重建,其余等待。
// SETNX 单线程重建
$lock = $redis->setnx("lock:u:$id", 1);
if ($lock) {
$redis->expire("lock:u:$id", 10);
$data = $db->find($id);
$redis->setex("u:$id", 3600, json_encode($data));
}
三、缓存雪崩:大量key同时过期
大批缓存同一时间失效,数据库承压暴增。和击穿区别:击穿是单个热点,雪崩是大面积。
- 防护:过期时间加随机偏移,避免同时失效;多级缓存;Redis集群高可用;限流降级兜底。
$ttl = 3600 + mt_rand(0, 600); // 3600~4200 秒随机
$redis->setex("list:$page", $ttl, json_encode($rows));
四、三者对比速记
- 穿透:数据不存在 → 布隆过滤器 + 缓存空值;
- 击穿:热点key过期 → 互斥锁 + 逻辑过期;
- 雪崩:大批key同时过期 → TTL随机 + 高可用 + 限流。
总结
先分清"查不存在""单点过期""批量过期"三种场景,再对应布隆过滤器、互斥锁、随机TTL,就能把数据库挡在缓存后面。
遇到问题加QQ23979811 协助处理
Nginx代理WebSocket
Nginx代理WebSocket
引言
聊天、实时推送、在线协作这类场景用WebSocket长连接。前端经过Nginx访问后端WS服务时,如果只按普通HTTP反代配置,握手会失败,因为WebSocket要先发起Upgrade协议升级,Nginx必须把升级头透传给后端。
WebSocket握手原理
客户端先发HTTP请求,带Upgrade: websocket和Connection: Upgrade头,后端同意后把HTTP协议升级成持久双向长连接。Nginx默认不透传这两个头,导致握手400。
标准代理配置
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
- proxy_http_version 1.1:WS基于HTTP/1.1;
- Upgrade $http_upgrade:透传客户端升级协议;
- Connection "upgrade":标记连接升级;
- read_timeout拉长:长连接默认60秒无数据会被Nginx断开。
用map统一处理
不同location可能同时有HTTP和WS,写map避免硬编码:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# location内
proxy_set_header Connection $connection_upgrade;
普通HTTP请求$http_upgrade为空时走close,WS请求走upgrade,一套配置兼容。
常见问题
- 握手400/502:没加Upgrade和Connection头;
- 连接几十秒自动断开:read_timeout太短,按需拉长或服务端发心跳;
- wss(HTTPS)走Nginx终止TLS后,后端用ws即可;
- 后端端口和proxy_pass写对,别漏http://。
注意事项
- 长连接超时靠心跳和read_timeout共同维持;
- 集群WS要配合粘性会话或后端共享状态,否则消息发不到正确连接;
- HTTPS页面必须用wss://,浏览器会拦截混合内容;
- 改配置nginx -t后reload。
总结
Nginx代理WebSocket核心是加proxy_http_version 1.1、透传Upgrade和Connection头、拉长read_timeout,用map兼容普通HTTP。握手失败先查这三个头,断线先查超时和心跳。
Docker清理悬空镜像
Docker清理悬空镜像
引言
反复build镜像后,旧的无标签镜像(
先看占用
docker system df
查看镜像、容器、卷、构建缓存各占多少,先摸清再动手。
什么是悬空镜像
build出新标签后,旧层变成
只清悬空镜像(安全)
docker image prune
只删除无标签无引用的dangling镜像,已打标签和在用镜像不动,最稳妥。
清停止的容器
docker container prune
删掉所有已退出的容器,正在运行的不受影响。
清无用镜像和构建缓存(更彻底)
docker system prune
一次清悬空镜像、停止容器、无用网络和构建缓存,加-a连未被容器使用的镜像也删,加--volumes连未使用卷一起删:
docker system prune -a --volumes
手动删单个镜像
docker image rm 镜像ID
若提示被容器引用,先确认容器用途,别直接强制删。
注意事项
- 生产慎用-a和--volumes,卷里可能存数据库数据,删了不可恢复;
- 先docker system df看占用,再决定清哪一类;
- 清理前确认没有需要保留的停止容器和备份卷;
- 镜像仓库里还能重新拉取的镜像才适合-a全清;
- 定期配合定时任务清理,避免磁盘缓慢耗尽。
总结
日常用docker image prune清悬空镜像最安全,docker system prune清停止容器和缓存,-a和--volumes只在确认无保留价值时用。先df看占用、分级清理,避免误删业务卷和在用镜像。
PHP二维数组排序
PHP二维数组排序
引言
从数据库查出的列表通常是二维数组,要按价格、销量、时间某一字段升降序排列,单维的sort/rsort直接用会把结构打乱。需要按指定键排序,常用array_multisort和usort两种思路。
array_multisort按字段排
$goods = [
['name' => 'A商品', 'price' => 99],
['name' => 'B商品', 'price' => 39],
['name' => 'C商品', 'price' => 69],
];
// 抽出price列作为排序依据
$price = array_column($goods, 'price');
array_multisort($price, SORT_DESC, $goods);
print_r($goods); // 价格从高到低
array_column先取单列,multisort按该列降序,主数组同步跟着重排。
多字段组合排序
先按销量降序,销量相同再按价格升序:
$sales = array_column($goods, 'sales');
$price = array_column($goods, 'price');
array_multisort($sales, SORT_DESC, $price, SORT_ASC, $goods);
usort自定义比较
规则复杂或中文拼音排序时用usort:
usort($goods, function($a, $b) {
return $a['price'] <=> $b['price']; // 升序
});
太空船运算符<=>比较大小,降序交换$a、$b即可。
注意事项
- array_multisort会改变原数组顺序,需要原数据先备份;
- 比较的字段类型要一致,避免字符串和数字混比;
- usort是稳定排序,同值元素相对位置保留;
- 中文排序可配合intl扩展或自定义映射,别直接靠默认strcmp;
- 数据量大时,优先在SQL层ORDER BY,PHP排序只做内存小列表。
总结
简单单字段排序用array_column加array_multisort,多字段依次传入,复杂规则用usort加<=>比较。能在MySQL ORDER BY解决的就别把数据拉到PHP再排,减轻内存压力。
Linux查找大文件
Linux查找大文件
引言
服务器磁盘爆满时,df先看哪个分区满,再用du一层层往下钻,找到吃掉空间的大目录和大文件。很多人只会df -h,却定位不到具体是哪个文件,这里讲完整排查链路。
第一步看分区占用
df -h
看Use%和挂载点,确认是/、/data还是/var满,别在没满的分区里瞎找。
第二步定位大目录
du -sh /* 2>/dev/null | sort -rh | head
du -sh统计每个一级目录大小,sort -rh按大小倒序,head取前几名,快速锁定大头目录,再逐层深入:
du -sh /var/* 2>/dev/null | sort -rh | head
第三步找超过100M的单文件
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null
-size +100M表示大于100MB,2>/dev/null丢弃无权限报错,直接列出大文件路径和大小。
常见膨胀位置
- /var/log:日志没轮转,单个日志几个G;
- /tmp:临时文件没清理;
- Docker:/var/lib/docker镜像和容器层堆积;
- 网站上传目录、数据库binlog。
处理建议
- 日志优先清空而不是删文件(> log.log),避免占用文件句柄不释放;
- Docker用docker system prune清理悬空镜像;
- 删之前确认文件没被进程占用,lsof | grep 文件名;
- 不要直接rm正在写入的日志,用truncate -s 0截断。
注意事项
- du统计含磁盘占用,find看文件真实大小,两者结合;
- 根目录全盘find慢,先缩小到df定位的分区;
- 系统目录(/usr、/lib)别乱删,只清理业务数据和日志。
总结
df -h定位满的分区,du -sh逐层排序找大目录,find -size找大文件,先看日志和Docker,清空用truncate别rm占用文件。按分区→目录→文件三层下钻,几分钟就能找到磁盘元凶。
MySQL自增ID重置
MySQL自增ID重置
引言
测试表删完数据后,下一条插入的自增ID还接着上次的数往上跳,演示或本地开发时想让ID从1重新开始,就要重置AUTO_INCREMENT。DELETE清空不会归零,这是新手最容易踩的坑。
DELETE不会重置自增
DELETE FROM users;
INSERT INTO users(name) VALUES('张三');
-- 新行ID仍然接着旧最大值递增,不从1开始
DELETE只删行,自增计数器保留在内存和表结构里,不受影响。
TRUNCATE直接归零
TRUNCATE TABLE users;
TRUNCATE相当于重建空表,自增计数清零、表结构保留,速度远快于全表DELETE,但会清空全部数据且不可回滚,谨慎使用。
只改起始值不清数据
表里还有数据时,把自增起点设为当前最大值+1:
ALTER TABLE users AUTO_INCREMENT = 1;
若表内已有大于1的数据,MySQL会自动取max(id)+1作为真实起点,不会产生主键冲突。
查看当前自增值
SHOW TABLE STATUS LIKE 'users';
-- 或
SELECT AUTO_INCREMENT FROM information_schema.TABLES
WHERE TABLE_NAME='users';
注意事项
- 生产环境不要随意TRUNCATE,误删数据无法恢复;
- 自增ID一般不连续,删行出现空洞属正常,不必追求从1排列;
- 用DELETE后想重置,单独ALTER设置AUTO_INCREMENT;
- InnoDB重启后自增值从max(id)重算,与MyISAM行为不同;
- 若ID被其他表外键引用,重置前先确认依赖。
总结
DELETE不清零自增,TRUNCATE才归零重建表;保留数据用ALTER TABLE ... AUTO_INCREMENT=1设起点,查当前值用SHOW TABLE STATUS。开发演示可重置,生产环境优先只调起点、避免TRUNCATE误删。
PHP字符串截取方法
PHP字符串截取方法
引言
截取标题摘要、手机号中间脱敏、按字数截断列表页,是PHP开发里最高频的操作。很多人直接用substr处理中文,结果出现半个汉字的乱码,关键在于substr按字节切,中文UTF-8占3个字节。
substr按字节截取
$str = "hello世界";
echo substr($str, 0, 5); // 输出 hello,中文场景容易切坏
substr只适合纯英文ASCII,遇到中文按字节偏移,可能把一个汉字拆成两半。
mb_substr按字符截取
处理中文要用多字节扩展mbstring:
echo mb_substr("hello世界", 0, 5, 'UTF-8');
第四个参数指定编码UTF-8,按"字"而不是"字节"切,中文完整不烂。
带省略号的摘要截断
function cutText($text, $len = 20) {
if (mb_strlen($text, 'UTF-8') <= $len) return $text;
return mb_substr($text, 0, $len, 'UTF-8') . '...';
}
先mb_strlen算字符长度,超长再截并补省略号,列表页摘要常用。
手机号中间脱敏
$phone = "13812345678";
echo substr($phone, 0, 3) . '****' . substr($phone, -4);
// 138****5678
手机号纯数字,substr完全够用,用-4取后四位。
注意事项
- 中文必须mb_substr并显式传UTF-8,别依赖默认编码;
- 确认服务器装了mbstring扩展,php -m查看;
- 表情、生僻字占多个字节,更要按字符切;
- 截断前先判断长度,避免补省略号反而超长。
总结
纯英文数字用substr,含中文统一mb_substr指定UTF-8,摘要先mb_strlen判断再补省略号,手机号脱敏用substr取头尾。选对函数就不会出现半个汉字乱码。
MySQL连接查询用法
MySQL连接查询用法
引言
业务数据分散在用户表、订单表、商品表中,要一次性取出用户姓名加订单金额,就要用JOIN把多张表按关联字段拼起来。分清内连接和外连接,是写对多表查询的关键。
内连接 INNER JOIN
只保留两表匹配上的行:
SELECT u.name, o.amount, o.order_no
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
没有订单的用户不会出现,没有对应用户的订单也不会出现。
左连接 LEFT JOIN
以左表为主,左表全部保留,右表匹配不上补NULL:
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
每个用户都列出来,没下单的用户amount显示NULL,常用于统计哪些用户没购买。
右连接 RIGHT JOIN
以右表为主,和左连接方向相反,实际项目多数统一用LEFT JOIN,可读性更好。
多表连接示例
SELECT u.name, p.title, o.amount
FROM orders o
INNER JOIN users u ON o.user_id = u.id
INNER JOIN products p ON o.product_id = p.id
WHERE o.status = 1
ORDER BY o.create_time DESC;
注意事项
- 关联字段类型要一致,避免隐式转换导致索引失效;
- ON写连接条件,WHERE过滤结果,别把连接条件误放WHERE导致LEFT JOIN变内连接;
- 大表JOIN先确认关联字段有索引;
- 多表查询用表别名,字段加别名前缀避免歧义;
- 不建议无关联条件的多表笛卡尔积。
总结
INNER JOIN取交集,LEFT JOIN保留左表全部。写多表查询先确定以哪张表为主,连接条件放ON,过滤条件放WHERE,关联字段建索引,避免隐式转换和笛卡尔积。
Nginx静态缓存配置
Nginx静态缓存配置
引言
CSS、JS、图片、字体这类不变的静态资源,每次都回源取一遍浪费带宽和时间。用Nginx给静态资源加浏览器缓存响应头,让浏览器本地留存,第二次访问直接读本地,首屏和重复访问速度明显提升。
配置示例
在server或location块中:
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
参数说明
- expires 30d:设置缓存30天,浏览器本地留存;
- Cache-Control public:允许中间缓存(CDN、代理);
- immutable:文件内容不变,缓存期内不再发条件请求;
- access_log off:静态资源不记访问日志,减少IO。
带版本号更新
静态文件内容更新后,旧缓存会导致用户看到老文件。常用办法是文件名带哈希或版本,如app.[hash].js,内容一变文件名就变,浏览器当成新文件重新下载:
# 配合构建工具输出的带hash文件名,缓存可拉长到一年
expires 1y;
动态文件不要长缓存
HTML页面、接口响应要短缓存或不缓存,避免用户看到过期页面:
location / {
add_header Cache-Control "no-cache";
}
验证方法
浏览器F12网络面板看响应头:Cache-Control、Expires,再看状态码,二次访问200 from disk/memory cache即生效。
注意事项
- 缓存过期时间按资源稳定性设置,图片长、HTML短;
- 更新静态资源靠改文件名版本号,而不是缩短缓存;
- 不要给动态接口、登录页面加public长缓存;
- 改配置后nginx -t检查再reload。
总结
Nginx静态缓存用expires加Cache-Control给css/js/图片设置本地缓存,稳定资源配合版本号长缓存,动态页面不缓存。浏览器F12验证响应头,平衡访问速度与内容新鲜度。
PHP数组去重排序
PHP数组去重排序
引言
数组是PHP最常用的数据结构,去重和排序是开发中反复出现的基础需求。掌握内置函数的正确用法,比自己写循环更高效,也更不容易出错。
一维数组去重
$arr = [3, 1, 2, 3, 1, 5];
$unique = array_unique($arr);
//结果保留首次出现的值,键名不变
注意array_unique默认按字符串比较,去重后键名可能不连续,需要重新索引:
$unique = array_values(array_unique($arr));
关联数组按字段去重
$users = [
['id'=>1,'name'=>'张三'],
['id'=>2,'name'=>'李四'],
['id'=>1,'name'=>'张三副本'],
];
$seen = [];
$unique = array_filter($users, function($item) use (&$seen) {
if (in_array($item['id'], $seen)) return false;
$seen[] = $item['id'];
return true;
});
排序函数区分
- sort():值升序,重置键名;
- rsort():值降序;
- asort():值升序,保留键名;
- ksort():按键名升序;
- krsort():按键名降序。
$score = ['张三'=>88,'王五'=>95,'李四'=>76];
arsort($score); //按分数降序,保留姓名键名
print_r($score);
自定义排序
$users = [['age'=>20],['age'=>35],['age'=>28]];
usort($users, function($a, $b) {
return $a['age'] <=> $b['age']; //太空船运算符比较
});
注意事项
- array_unique对多维数组不能直接按字段去重;
- sort系列会改变原数组,返回bool;
- 中文排序建议用Collator或mb方式;
- 去重后及时array_values重置索引,避免后续遍历异常。
总结
一维去重用array_unique加array_values,多维按字段用array_filter配合标记数组。排序先想清是否保留键名,普通值排序用sort/rsort,关联数组用asort/ksort,复杂规则用usort自定义比较。
Go语言channel缓冲与阻塞原理详解
前言
channel 是 Go 语言 goroutine 之间通信的核心机制,很多新手写并发程序时会遇到 goroutine 阻塞、死锁、数据写不进去等问题,根源都是没搞懂无缓冲通道和有缓冲通道的阻塞规则。本篇结合代码讲清 channel 的缓冲模型与阻塞时机。
一、无缓冲通道(unbuffered channel)
ch := make(chan int)
go func() {
ch <- 100 // 发送
}()
v := <-ch // 接收
无缓冲通道容量为 0,发送方必须等到有接收方准备好才会完成发送,属于"同步握手"。如果只发不收,发送 goroutine 会永久阻塞。
二、有缓冲通道(buffered channel)
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// 再发第4个会阻塞,直到有人接收腾出位置
容量为 3 时,前 3 个元素先进缓冲区,发送方不会阻塞;缓冲区满了之后,发送方才会等待。接收方在缓冲区为空时阻塞。
三、阻塞与关闭的常见规则
- 向已关闭的 channel 发送数据会 panic;
- 从已关闭的 channel 接收会得到零值,ok 标识为 false;
- 关闭后再次关闭会 panic;
- 遍历 channel 用 range,channel 关闭后 range 自动退出。
四、常见踩坑
- 忘记接收导致发送 goroutine 泄漏,程序卡住;
- 缓冲容量设置过大失去解耦意义,过小又频繁阻塞;
- 主 goroutine 提前退出,子协程数据没发完就被回收。
总结
无缓冲通道做同步通信,有缓冲通道做异步削峰。理解"缓冲区满则发送阻塞、空则接收阻塞"这两条规则,就能避开大部分 channel 死锁问题。
遇到问题加QQ23979811 协助处理
Git撤销提交操作
Git撤销提交操作
引言
提交错了、提交了不该上传的文件、commit message写错,都是日常开发常见情况。Git提供多种撤销方式,关键是分清:还没push的本地提交和已经推到远程的提交,处理方式完全不同。
场景一:改错还没add
工作区改动想放弃:
git checkout -- 文件名 #旧写法
git restore 文件名 #新写法
整个工作区全部还原:git restore .
场景二:add了但没commit
把文件撤出暂存区,保留改动:
git reset HEAD 文件名
git restore --staged 文件名
场景三:commit了但没push
改最后一次提交信息:
git commit --amend -m "新的提交说明"
撤销最近一次提交,改动留在工作区:
git reset --soft HEAD^ #撤销提交,代码保留在暂存区
git reset --mixed HEAD^ #默认,撤销提交,代码退回工作区
git reset --hard HEAD^ #撤销提交并丢弃改动,慎用
场景四:已经push到远程
本地reset后强推会改写历史,团队共用分支风险大,推荐用反向提交:
git revert 提交ID
revert会生成一个新提交,把那次改动反向抵消,不改写历史,安全协作。
注意事项
- --hard会丢代码,操作前先备份或开分支;
- 已push到共享分支不要随意reset强推;
- amend只用于未推送的本地提交;
- 查看历史用git log --oneline定位提交ID。
总结
未add用restore,add未commit用reset HEAD撤出,本地commit用reset(soft/mixed保留代码,hard丢弃),已push用revert新增反向提交。记住本地和远程的区别,避免改写团队历史。
MySQL事务ACID原理
MySQL事务ACID原理
引言
转账、下单、扣库存这类多步操作必须保证要么全成功要么全回滚,靠的就是数据库事务。ACID是事务四个特性的缩写,理解它才能用对事务、排查并发问题。
四个特性
A 原子性 Atomicity
事务内操作不可分割,全部成功提交,失败则整体回滚到事务前状态。InnoDB靠undo log回滚日志实现,记录修改前旧值,出错反向撤销。
C 一致性 Consistency
事务前后数据库从一个合法状态转到另一个合法状态,约束(主键、外键、唯一、余额非负)始终不被破坏。一致性是最终目标,靠ABD共同保障。
I 隔离性 Isolation
并发事务之间互不干扰,隔离级别从低到高:读未提交、读提交、可重复读(InnoDB默认)、串行化,用锁和MVCC实现。
D 持久性 Durability
事务一旦提交,修改永久落盘,宕机也不丢。靠redo log重做日志,先写日志再刷盘,崩溃后重放恢复。
事务用法示例
START TRANSACTION;
UPDATE account SET money = money - 100 WHERE id = 1;
UPDATE account SET money = money + 100 WHERE id = 2;
COMMIT;
-- 出错则 ROLLBACK;
PHP中配合PDO:
$pdo->beginTransaction();
try {
$pdo->exec("UPDATE account SET money=money-100 WHERE id=1");
$pdo->exec("UPDATE account SET money=money+100 WHERE id=2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
注意事项
- 事务不要包太长,避免长时间持锁;
- 只对必须一致的操作开事务;
- 一致性靠应用校验和数据库约束共同保证;
- 隔离级别越高并发越低,按业务权衡。
总结
ACID:原子性靠undo log、持久性靠redo log、隔离性靠锁与MVCC、一致性是目标。转账扣库存等关键链路用事务包裹,注意控制事务长度和隔离级别。
Nginx开启Gzip压缩
Nginx开启Gzip压缩
引言
HTML、CSS、JS、JSON这类文本资源直接传输体积大,首屏慢。Nginx开启Gzip后,服务端先压缩再发送,浏览器解压,通常文本体积能减少60%以上,是零成本提升访问速度的手段。
配置示例
在http块或server块加入:
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
参数说明
- gzip on:开关;
- gzip_min_length:小于1k不压缩,避免小文件压缩得不偿失;
- gzip_comp_level:压缩等级1-9,6比较均衡,太高CPU占用大;
- gzip_types:指定压缩的MIME类型,text/html默认自带;
- gzip_vary on:响应加Vary:Accept-Encoding,避免缓存问题;
- gzip_disable:兼容老旧IE浏览器。
生效验证
改完执行nginx -t检查语法,再reload。浏览器F12网络面板看响应头Content-Encoding:gzip即生效。
注意事项
- 图片、视频、PDF本身已压缩,不要再gzip,浪费CPU;
- 压缩等级不要盲目拉满,大文件压缩吃CPU;
- 静态资源可在CDN层提前压缩;
- 配合Brotli现代压缩效果更好,需Nginx额外模块支持。
总结
Gzip是文本类资源提速标配,开启后配置类型、大小阈值、合理压缩等级,reload生效,用响应头验证。不压缩图片视频,平衡传输体积与服务器CPU。
Docker容器日志查看
Docker容器日志查看
引言
容器启动报错、服务运行异常时,不能进容器盲目找文件,第一时间用docker logs查看标准输出日志。应用把日志打到stdout/stderr,Docker统一收集,是容器排错最常用入口。
基础查看命令
#查看全部日志
docker logs 容器名或ID
#实时跟踪最新日志,类似tail -f
docker logs -f 容器名
#只看最近100行
docker logs --tail 100 容器名
#查看某时间之后日志
docker logs --since 30m 容器名
docker logs --since 2026-10-18T07:00:00 容器名
#带时间戳
docker logs -t 容器名
常用组合
#实时跟踪并显示时间戳、最近50行
docker logs -f -t --tail 50 myapp
排查重启循环、接口报错时,先tail看最后报错,再结合时间过滤。
日志原理
Docker默认把容器标准输出保存到本地json-file驱动,存在宿主机/var/lib/docker/containers目录。应用要把日志输出到控制台,而不是只写容器内文件,才能被docker logs抓到。
日志注意事项
- 默认json-file不限制大小,长时间运行会占满磁盘,生产配置日志轮转:
{"log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"}} - 需要长期分析的日志,接入ELK等集中日志系统;
- 容器删除后默认日志随之丢失;
- 应用内部业务日志建议挂载到宿主机目录持久化。
总结
docker logs是容器排错第一命令,-f跟踪、--tail取尾部、--since按时间过滤。生产配置大小轮转避免磁盘写满,业务重要日志持久化或集中采集。
JS跨域CORS方案
JS跨域CORS方案
引言
浏览器同源策略要求协议、域名、端口完全一致,前端页面访问另一个域名的接口就会被拦截报跨域错误。CORS是目前最标准、主流的跨域解决方式,由后端配合响应头放行。
什么是同源策略
三个任意一项不同都算跨域:
- 协议不同(http/https);
- 域名不同;
- 端口不同。
CORS后端响应头(PHP)
<?php
header('Access-Control-Allow-Origin: https://www.example.com');
header('Access-Control-Allow-Methods: GET,POST,OPTIONS');
header('Access-Control-Allow-Headers: Content-Type,Authorization');
header('Access-Control-Allow-Credentials: true');
携带cookie时Allow-Origin不能写*,必须指定具体域名,且前端请求withCredentials为true。
预检请求OPTIONS
POST JSON等复杂请求,浏览器会先发OPTIONS预检,后端要直接放行OPTIONS,返回204,否则正式请求发不出去。
其他常见跨域方式对比
- JSONP:只支持GET,老旧方案;
- Nginx反向代理:前端请求同源nginx,由nginx转发接口,无浏览器跨域;
- 开发环境代理:Vite、Webpack devServer proxy临时解决。
注意事项
- 生产不要用*放开所有来源;
- 区分简单请求和预检请求;
- 配合cookie要带Credentials并指定来源;
- 接口服务端做好来源校验。
总结
CORS是前后端分离首选方案,核心是后端设置允许来源、方法、响应头。复杂请求先处理OPTIONS预检,带cookie时指定具体域名,安全可控,比JSONP和代理更规范。
PHP错误日志排查
PHP错误日志排查
引言
线上页面白屏、500错误,前台看不到具体原因,靠猜很难定位。正确做法是开启并查看PHP错误日志,从日志里拿到文件、行号、错误类型,再针对性修复。
先确认日志位置
在phpinfo页面搜索error_log,或查看php.ini配置:
#查看php.ini错误日志配置
php -i | grep error_log
#常见位置
tail -f /var/log/php-fpm/www-error.log
tail -f /var/log/httpd/php_errors.log
宝塔、面板环境一般在日志菜单里可直接打开PHP错误日志。
开发环境临时显示错误
<?php
ini_set('display_errors',1);
ini_set('error_reporting',E_ALL);
error_reporting(E_ALL);
生产环境不要直接display_errors到页面,统一写日志。
日志格式解读
一条错误日志通常包含:错误级别、报错文件、行号、错误信息。
[12-Oct-2026 07:00:00] PHP Fatal error:
Call to undefined function xxx() in /www/app/index.php on line 18
含义:第18行调用了不存在的函数,检查函数是否引入、命名是否拼错。
常见错误类型
- Fatal error:致命错误,程序中断,如调用不存在的类、函数;
- Warning:警告不中断,如参数类型不对;
- Notice:提示,未定义变量;
- Deprecated:废弃语法,未来版本不兼容。
排查流程
- 复现问题,记录访问时间;
- 按时间过滤日志,定位对应报错;
- 打开报错文件对应行号;
- 修复后清空日志再验证。
总结
PHP排错第一步永远是看错误日志,而不是反复刷新页面。开发环境显示错误,生产环境写日志,按时间和行号快速定位,养成记录日志的习惯。
Linux防火墙配置
Linux防火墙配置
引言
云服务器安全不能只靠系统防火墙,开通端口是上线必经步骤。CentOS 7以上默认使用firewalld,很多新手忘了放行端口,导致网站、SSH、数据库从外部连不上。
firewalld常用命令
#查看防火墙状态
firewall-cmd --state
#查看已放行端口
firewall-cmd --list-ports
#永久放行80、443端口
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
#重新加载生效
firewall-cmd --reload
#移除某个端口
firewall-cmd --permanent --remove-port=3306/tcp
区域概念
firewalld分public、internal等zone,默认public。--permanent写入永久规则,不加只对当前会话生效,重启失效。改完必须reload。
常用放行场景
- Web服务:80、443;
- SSH远程:22(建议改非默认端口再放行);
- 不要对公网开放3306、6379、27017等数据库端口;
- 只允许指定IP访问管理端口更安全:
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=1.2.3.4 port port=22 protocol=tcp accept'
注意事项
- 云服务器除了系统防火墙,还要在云厂商控制台安全组同步放行;
- 改SSH端口前先确认新端口放行,避免把自己挡在门外;
- 防火墙规则最小化,只开放必要端口。
总结
firewalld核心:查看列表、永久加端口、reload生效。记住系统防火墙和云安全组两层都要放行,数据库端口绝不暴露公网,管理端口限制来源IP。
Composer国内镜像配置
Composer国内镜像配置
引言
PHP项目用Composer安装依赖时,默认从packagist.org国外源拉取,国内网络经常超时、下载极慢。配置国内镜像源后,安装速度能提升数倍,是PHP开发环境必备操作。
全局配置方法
终端执行下面命令,把全局镜像换成国内源:
composer config -g repos.packagist composer https://mirrors.aliyun.com/composer/
执行后写入用户全局配置,所有项目生效。
单个项目配置
只给当前项目换源,在项目目录执行:
composer config repositories.packagist composer https://mirrors.aliyun.com/composer/
常见国内镜像
- 阿里云镜像:https://mirrors.aliyun.com/composer/
- 腾讯云镜像:https://mirrors.cloud.tencent.com/composer/
- 华为云镜像:https://repo.huaweicloud.com/repository/php/
验证与切换
#查看当前源
composer config -g repos
#回到官方源
composer config -g --unset repos.packagist
注意事项
- 镜像只加速包下载,不影响代码本身;
- 生产环境尽量锁定composer.lock版本;
- 个别新包国内镜像同步延迟,临时可切官方源;
- 配置后先清缓存再安装:
composer clearcache。
总结
Composer换源是国内PHP开发第一步,全局配置阿里云镜像即可解决大部分超时问题。安装依赖前先清缓存,项目锁定lock保证团队版本一致。