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。


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

Redis缓存穿透击穿雪崩区别与防护方案

前言

Redis 把热点数据放进内存提速,但缓存一旦失效或被恶意穿透,数据库会被瞬间打垮。很多人把穿透、击穿、雪崩混为一谈,三者成因不同,防护手段也不一样,本篇一次讲清区别和方案。

一、缓存穿透:查不存在的数据

攻击者或程序反复查询一个数据库里根本不存在的 key,缓存永远不命中,每次都打到 MySQL。

  • 典型:刷不存在的用户ID、商品ID。
  • 防护:
    1. 布隆过滤器(Bloom Filter):请求先过过滤器,不存在直接拦截,不查库;
    2. 缓存空值:对查不到的结果也缓存一个空对象并设短过期时间(如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 突然过期,大量并发同时打到数据库重建缓存。和穿透区别:数据真实存在,只是刚好过期。

  • 防护:
    1. 热点key逻辑过期,不设物理TTL,后台异步刷新;
    2. 加互斥锁(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 协助处理


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

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。握手失败先查这三个头,断线先查超时和心跳。


emer 发布于  2026-10-19 07:22 

Docker清理悬空镜像

Docker清理悬空镜像

引言

反复build镜像后,旧的无标签镜像()一层层堆积,/var/lib/docker占满磁盘。这些悬空镜像没被任何容器引用,可以安全清理,但要分清悬空、停止容器、无用网络和构建缓存,别一刀切删错。

先看占用

docker system df

查看镜像、容器、卷、构建缓存各占多少,先摸清再动手。

什么是悬空镜像

build出新标签后,旧层变成:,没有标签、不被容器使用,就是dangling悬空镜像。

只清悬空镜像(安全)

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看占用、分级清理,避免误删业务卷和在用镜像。


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

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再排,减轻内存压力。


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

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占用文件。按分区→目录→文件三层下钻,几分钟就能找到磁盘元凶。


emer 发布于  2026-10-19 07:18 

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误删。


emer 发布于  2026-10-19 07:17 

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取头尾。选对函数就不会出现半个汉字乱码。


emer 发布于  2026-10-19 07:16 

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,关联字段建索引,避免隐式转换和笛卡尔积。


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

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验证响应头,平衡访问速度与内容新鲜度。


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

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自定义比较。


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

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 个元素先进缓冲区,发送方不会阻塞;缓冲区满了之后,发送方才会等待。接收方在缓冲区为空时阻塞。

三、阻塞与关闭的常见规则

  1. 向已关闭的 channel 发送数据会 panic;
  2. 从已关闭的 channel 接收会得到零值,ok 标识为 false;
  3. 关闭后再次关闭会 panic;
  4. 遍历 channel 用 range,channel 关闭后 range 自动退出。

四、常见踩坑

  • 忘记接收导致发送 goroutine 泄漏,程序卡住;
  • 缓冲容量设置过大失去解耦意义,过小又频繁阻塞;
  • 主 goroutine 提前退出,子协程数据没发完就被回收。

总结

无缓冲通道做同步通信,有缓冲通道做异步削峰。理解"缓冲区满则发送阻塞、空则接收阻塞"这两条规则,就能避开大部分 channel 死锁问题。

遇到问题加QQ23979811 协助处理


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

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新增反向提交。记住本地和远程的区别,避免改写团队历史。


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

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、一致性是目标。转账扣库存等关键链路用事务包裹,注意控制事务长度和隔离级别。


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

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。


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

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按时间过滤。生产配置大小轮转避免磁盘写满,业务重要日志持久化或集中采集。


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

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和代理更规范。


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

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:废弃语法,未来版本不兼容。

排查流程

  1. 复现问题,记录访问时间;
  2. 按时间过滤日志,定位对应报错;
  3. 打开报错文件对应行号;
  4. 修复后清空日志再验证。

总结

PHP排错第一步永远是看错误日志,而不是反复刷新页面。开发环境显示错误,生产环境写日志,按时间和行号快速定位,养成记录日志的习惯。


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

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。


emer 发布于  2026-10-18 07:22 

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/

常见国内镜像

验证与切换

#查看当前源
composer config -g repos

#回到官方源
composer config -g --unset repos.packagist

注意事项

  • 镜像只加速包下载,不影响代码本身;
  • 生产环境尽量锁定composer.lock版本;
  • 个别新包国内镜像同步延迟,临时可切官方源;
  • 配置后先清缓存再安装:composer clearcache。

总结

Composer换源是国内PHP开发第一步,全局配置阿里云镜像即可解决大部分超时问题。安装依赖前先清缓存,项目锁定lock保证团队版本一致。


emer 发布于  2026-10-18 07:21 
'); })();