PHP获取客户端IP

PHP获取客户端IP

引言

记录访问日志、做地域统计或IP限定时要拿真实访客IP。经过Nginx反代或CDN后,REMOTE_ADDR是代理地址,真实IP藏在转发头里,直接取单一字段容易拿到代理IP,需要按可信链兜底并校验。

直连场景

$ip = $_SERVER['REMOTE_ADDR'] ?? '';

没有反代时,这就是直连客户端IP。

反代后取真实IP

$headers = [
    'HTTP_X_FORWARDED_FOR',
    'HTTP_X_REAL_IP',
    'HTTP_CLIENT_IP',
];
foreach ($headers as $h) {
    if (!empty($_SERVER[$h])) {
        $ip = explode(',', $_SERVER[$h])[0];
        break;
    }
}

X-Forwarded-For可能是逗号分隔的IP链,取第一个才是原始客户端。

封装函数

function getClientIp(): string {
    foreach (['HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP'] as $h) {
        if (!empty($_SERVER[$h])) {
            $ip = trim(explode(',', $_SERVER[$h])[0]);
            if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
        }
    }
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

filter_var校验格式,非法就回退到REMOTE_ADDR。

代理头必须由服务端写入

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

头要由可信Nginx设置,不能完全信任客户端自带。

注意事项

  • XFF是逗号链,取最左原始IP;
  • 客户端可伪造XFF,只信任自己配置的代理;
  • filter_var校验IPv4/IPv6格式;
  • 不要用伪造IP做安全鉴权,登录风控仍需服务端;
  • CDN场景真实IP在CDN回源头,需查厂商文档;
  • 内网代理REMOTE_ADDR是网关IP,别当访客IP。

总结

直连取REMOTE_ADDR,反代后从X-ForwardedFor、X-Real-IP按序兜底,XFF逗号链取第一个,filter_var校验后回退,代理头由可信Nginx写入。关键是客户端可伪造转发头,只信任自己配置的代理链,IP适合统计不适合单独做安全判定,CDN需用厂商回源头。


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

Nginx绑定域名配置

Nginx绑定域名配置

引言

一台服务器跑多个站点,靠server_name区分访问哪个域名,配合DNS解析和站点根目录,Nginx按Host头把请求路由到对应虚拟主机。配置顺序写错或server_name不匹配,会落到默认站点。

基础域名站点

server {
    listen 80;
    server_name www.example.com example.com;
    root /var/www/example;
    index index.html index.php;
}

server_name可并列写主域名和裸域名,root指向站点目录。

裸域名跳转到www

server {
    listen 80;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

避免两个域名都解析内容造成重复收录,统一到一个规范域名。

多个域名并列

server_name api.example.com admin.example.com;

同一套站点对应多个子域名时直接空格分隔。

反向代理绑定域名

server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
    }
}

域名指向后端服务,记得把Host头透传给上游。

配置后检查重载

nginx -t
nginx -s reload

先测语法再平滑重载,reload不断连接。

匹配规则

  • 精确域名优先,然后通配符,最后默认站点;
  • 没匹配上server_name的请求落到default_server;
  • 通配符*.example.com不能匹配example.com裸域;
  • 多站点注意别让无关域名被默认站点接住。

注意事项

  • DNS要先把A记录解析到服务器IP,否则域名打不开;
  • server_name区分大小写但通常小写;
  • 80改443需另配SSL证书块;
  • return 301统一域名利于SEO避免重复;
  • 防火墙和安全组放行80、443;
  • 多个server块listen 80,靠server_name分流。

总结

server_name在server块绑定域名,可并列多个,裸域301跳www统一规范,反代时透传Host,改完nginx -t检查再reload。匹配精确优先于通配符,未命中走默认站点,前提是DNS解析到位、端口放行,统一域名有助于SEO避免重复收录。


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

Git查看远程分支

Git查看远程分支

引言

团队协作时,远程仓库可能已经开了新分支或删了旧分支,本地缓存的远程引用不会自动更新。先fetch同步远端信息,再用-r参数列出远程分支,避免基于过期列表误操作。

列出所有远程分支

git branch -r

列出remote-tracking分支,origin/前缀代表远端,注意这是本地缓存的远端快照。

先同步再查看

git fetch origin
git branch -r

fetch拉取最新远端引用后再列,才能看到别人刚推上来的新分支。

本地加-r看全部

git branch -a

-a同时列本地分支和远程跟踪分支,方便对比两边差异。

查看远程分支最新提交

git log origin/main --oneline -5

不切分支也能看远程分支最近几条提交历史。

查看远程仓库地址

git remote -v

列出fetch和push两个地址,确认连接的是正确远端。

清理已删除的远程分支缓存

git fetch --prune origin

远端已删的分支,本地列表里还留着,prune清掉过期引用。

按关键词筛选

git branch -r | grep feature

分支多时用grep过滤feature、release等前缀。

注意事项

  • git branch -r看的是本地缓存,先fetch再看才准;
  • 远程分支不能直接checkout修改,要建本地跟踪分支;
  • origin/main是跟踪引用,不是独立可写分支;
  • prune清理远端已删分支的本地残留;
  • 切远程分支用git checkout -b 本地名 origin/远程名;
  • 多远端要指定remote名,默认origin。

总结

git branch -r列出本地缓存的远程跟踪分支,先git fetch同步最新,-a同时看本地远程,log看远程分支提交,remote -v看仓库地址,fetch --prune清理过期引用,grep筛选前缀。核心是列表是缓存快照,操作前先fetch,远程分支只读,改动需建本地跟踪分支再推送。


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

MySQL修改字段类型

MySQL修改字段类型

引言

上线后字段长度不够、类型选错是常见需求,ALTER TABLE MODIFY可以改字段类型,但收缩长度或跨类型可能丢数据,大表执行还会长时间锁表,动手前必须备份和评估。

修改字段类型与长度

ALTER TABLE users
MODIFY COLUMN phone VARCHAR(20) NOT NULL DEFAULT '';

MODIFY重写该列完整定义,要把NOT NULL、默认值、字符集一起带上,否则会被重置。

修改字段名:CHANGE

ALTER TABLE users
CHANGE COLUMN tel phone VARCHAR(20) NOT NULL;

CHANGE既要写旧名也要写新名,顺便改类型,MODIFY只改类型不改名。

数字类型扩容

ALTER TABLE orders
MODIFY COLUMN amount DECIMAL(12,2) NOT NULL;

金额从DECIMAL(10,2)扩到(12,2)属于安全扩容,精度扩大不丢数据。

字符串加长

ALTER TABLE posts
MODIFY COLUMN title VARCHAR(255) NOT NULL;

varchar加长一般安全,注意字符集下长度是字符数不是字节。

大表谨慎执行

# 大表避开高峰,用pt-online-schema-change或gh-ost在线改

直接ALTER在InnoDB可能重建整表并锁写,百万行以上务必在线工具。

改前必查

SELECT COUNT(*) FROM users WHERE CHAR_LENGTH(phone) > 20;

先查有没有超长数据,收缩长度前先清理,否则直接截断或报错。

注意事项

  • MODIFY要带全字段约束,别只写类型;
  • CHANGE改名+改类型,MODIFY只改类型;
  • 收缩长度、改窄类型可能截断丢数据,先备份;
  • 大表用在线DDL工具,避开高峰;
  • 改完用SHOW CREATE TABLE核对;
  • 类型转换不兼容(如字符串转数字)会失败或变0,先清洗数据。

总结

MODIFY改字段类型需重写完整定义含约束默认值,CHANGE可同时改名,数字扩容和字符串加长相对安全,收缩长度跨类型前先查超长数据并备份,大表用pt-osc或gh-ost在线改避免锁表,改完SHOW CREATE核对。生产改表先备份再评估锁表风险。


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

PHP空合并运算符

PHP空合并运算符

引言

从请求数组里取值时,直接访问不存在的键会触发Notice报错。isset加三元判断写法啰嗦,PHP7引入的??空合并运算符一行搞定,且不存在键时不报Notice,是接口参数处理的高频写法。

基本用法

$page = $_GET['page'] ?? 1;

键存在且非null就取它的值,否则取默认值1,全程不报Notice。

对比老式写法

// 旧写法,啰嗦且易漏isset
$page = isset($_GET['page']) ? $_GET['page'] : 1;

// 新写法,等价且简洁
$page = $_GET['page'] ?? 1;

两段逻辑完全等价,新写法可读性明显更好。

链式兜底

$name = $input['name'] ?? $request['name'] ?? '匿名用户';

依次尝试,第一个非null的值被采用,最后一个兜底默认值。

数组嵌套取值

$city = $data['user']['city'] ?? '未知城市';

中间任何一层键缺失都安全返回默认值,不会因层级不存在报错。

??与||的区别

// ??只判断null和未定义,空字符串0都保留
$q = $_GET['q'] ?? '';

// ||会把0、空字符串当假,业务语义不同

注意0和空字符串不是null,??会原样保留。

赋值场景:合并赋值

$config['debug'] ??= false;

??=只在键不存在或为null时赋值,已存在则不动。

注意事项

  • ??只在未定义或null时兜底,空串、0、false不算空;
  • 数组深层取值安全,但类型仍要自行校验;
  • 不要用??替代输入过滤,默认值之外仍需验证;
  • PHP5不支持??,老环境需改用isset三元;
  • 链式写法注意短路,前面命中即返回;
  • 业务关键参数兜底后仍要做合法性范围检查。

总结

??判断键是否存在或为null,缺省返回默认值且不报Notice,替代isset三元,支持链式兜底和数组深层取值,??=实现条件赋值。区别于||,0和空字符串会被保留不会被兜底,PHP5需用旧写法,取到默认值后关键参数仍要校验合法性。


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

Linux查看目录占用

Linux查看目录占用

引言

磁盘满了先定位是哪个目录吃掉空间,df看整体分区,du逐层下钻找大户。两个命令配合,从分区到目录到文件逐级缩小范围,是磁盘排查的标准路径。

看整体分区:df

df -h

人类可读格式显示各分区总量、已用、可用、使用率,先判断是哪个挂载点满了。

看单个目录总大小

du -sh /var/log

-s汇总,-h人类可读,直接给出这个目录总共占多大。

看一级子目录大小并排序

du -h --max-depth=1 /var | sort -rh | head

只算一层子目录,sort -rh按人类可读大小倒序,head取前几名,快速定位大户。

常用简写

du -sh /var/*

列出/var下每个一级目录大小,逐层进入继续排查。

排除文件系统统计

du -shx /home

-x不跨文件系统,避免把挂载的别的盘也算进来。

看目录下文件数量

ls /var/log | wc -l

有些目录不大但文件海量,inode耗尽也会写不进,配合df -i看inode。

注意事项

  • df看分区,du看目录,先df定位再du下钻;
  • 大目录du会遍历较慢,加--max-depth限制层数;
  • 删除大文件后空间没释放,可能是进程仍持有句柄,需重启进程;
  • sort -rh依赖du输出的G/M单位;
  • /proc、/sys是虚拟文件系统,du统计无意义;
  • 日志目录优先排查,常是增长最快的大户。

总结

df -h看分区整体,du -sh看单目录汇总,--max-depth=1配合sort -rh找一级大户,-x不跨文件系统,ls加wc -l看文件数,df -i查inode。排查路径是先df定位满的分区,再du逐层下钻,删文件后空间不释放要查持有句柄的进程,日志目录优先检查。


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

Redis查看键数量

Redis查看键数量

引言

排查内存占用、统计缓存规模、清理过期数据前,先要看清当前库有多少键、某前缀下有多少键。不同命令代价差别很大,生产环境绝对不能用KEYS全量扫描,否则会阻塞Redis。

统计当前库总键数:DBSIZE

DBSIZE

返回当前数据库的键总数,是O(1)操作,底层直接读计数器,生产环境可安全使用。

切换库再统计

SELECT 2
DBSIZE

Redis默认0到15共16个逻辑库,DBSIZE只统计当前选中的库。

统计指定前缀数量:SCAN配合

SCAN 0 MATCH user:* COUNT 100

SCAN游标式分批遍历,不会阻塞主线程,配合MATCH按前缀过滤,COUNT是每批建议数量而非精确数量。

错误做法:KEYS全扫

# 生产禁止
KEYS user:*

KEYS是O(n)一次性遍历全部键,键多时会长时间阻塞,线上直接用可能引发雪崩。

估算内存与键分布

INFO keyspace

一次性返回所有库的键数量、过期键比例、平均TTL,适合整体巡检。

按类型统计

SCAN 0 TYPE string COUNT 500

SCAN支持按数据类型过滤,筛选string、hash、list等。

脚本批量统计前缀

redis-cli --scan --pattern 'order:*' | wc -l

命令行用--scan流式遍历,管道交给wc计数,比KEYS安全。

注意事项

  • DBSIZE是O(1)放心用,KEYS是O(n)生产禁;
  • SCAN结果可能重复,业务去重;
  • DBSIZE不含已过期未清理键,近似看规模;
  • 多库要逐个SELECT统计;
  • 大库统计用INFO keyspace代替自己遍历;
  • 删前缀键也用SCAN加DEL,别配KEYS。

总结

DBSIZE O(1)看当前库总数,SELECT切库分别统计,指定前缀用SCAN游标遍历,生产禁用KEYS全扫,INFO keyspace看全库分布和TTL,命令行用--scan加wc计数。核心是别用阻塞命令,SCAN分批处理允许重复需去重,多库分别统计,清理键同样走SCAN。


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

MySQL创建数据库

MySQL创建数据库

引言

新项目第一步往往是建库,CREATE DATABASE语法简单,但字符集和排序规则选错会在后期引发中文乱码、表情存不下、排序不一致等麻烦,最好在建库时一次定好。

基本建库

CREATE DATABASE mydb;

用默认字符集建库,生产环境不建议这样,最好显式指定。

指定字符集建库(推荐)

CREATE DATABASE mydb
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

utf8mb4支持完整Unicode和emoji,general_ci不区分大小写,绝大多数Web项目够用。

判断不存在再建

CREATE DATABASE IF NOT EXISTS mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

IF NOT EXISTS防止重复执行报错,初始化脚本常用。

查看建库语句

SHOW CREATE DATABASE mydb;

确认字符集、排序规则和预期一致。

修改已有库字符集

ALTER DATABASE mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

只改库默认值,已有表和列的字符集不会跟着变,需逐表处理。

删除数据库

DROP DATABASE IF EXISTS mydb;

不可逆,删前必须备份,IF EXISTS避免报错中断脚本。

授权配合

CREATE USER 'appuser'@'localhost' IDENTIFIED BY '强密码';
GRANT ALL ON mydb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;

建库后给专用账号授权,不用root跑业务。

注意事项

  • 统一utf8mb4,别用过时的utf8(只占3字节);
  • 排序规则ci结尾不区分大小写,bin区分;
  • 库名小写、下划线分隔,避免跨平台大小写坑;
  • 业务账号最小权限,禁止给ALL on .;
  • ALTER库字符集不改存量表,需单独刷表;
  • 初始化脚本加IF NOT EXISTS保证可重复执行。

总结

CREATE DATABASE建库,生产显式指定utf8mb4和排序规则,IF NOT EXISTS让脚本可重复执行,SHOW CREATE核对,ALTER只改默认值,DROP不可逆先备份,配合专用账号最小授权。建库定好字符集能避免后期乱码,库名规范小写,权限按库授予,别用root直接跑业务。


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

PHP去除字符串空格

PHP去除字符串空格

引言

用户输入、表单提交常带首尾空格甚至全角空格、换行,入库前不清洗会导致登录比对失败、查询匹配不上、统计失真。trim系列处理首尾,preg_replace处理中间多余空格,按需选用。

去除首尾空白:trim

$name = trim("  张三  ");
// 结果:张三,首尾空格、制表符、换行一并去掉

trim默认去空格、tab、换行、回车、null字节等首尾空白。

只去左侧:ltrim

$path = ltrim("/var/www/", "/");
// 结果:var/www/,第二个参数指定要剥掉的字符集

只去右侧:rtrim

$file = rtrim("photo.jpg/", "/");
// 结果:photo.jpg/,只剥右边指定字符

自定义去除字符

trim($str, " \t\n,");

第二个参数列出要去除的字符,不止空格,可同时去逗号、换行。

去掉中间多余空格

$str = preg_replace('/\s+/', ' ', $str);
// 多个连续空白压成一个空格
$str = preg_replace('/\s/', '', $str);
// 彻底删除所有空格和空白

trim管首尾,中间空格必须用正则。

处理全角空格

$str = str_replace(' ', '', $str);
// 全角空格\u3000普通trim去不掉,要单独替换

数组批量清洗

$clean = array_map('trim', $_POST);

对整个表单数组逐个trim,登录注册入口常用。

注意事项

  • trim只处理首尾,别指望它删中间空格;
  • 全角空格普通trim无效,需str_replace单独处理;
  • 密码字段不要trim,避免误改用户输入;
  • 第二个参数是字符集不是整串,按字符逐个剥;
  • 入库前统一清洗,查询和比对才一致;
  • 中文人名中间正常空格要保留,只压连续空白。

总结

trim去首尾空白,ltrim去左,rtrim去右,第二个参数自定义字符集,中间连续空格用preg_replace压成一个或全删,全角空格单独str_replace,表单用array_map批量清洗。记住trim只管首尾,密码不清洗,全角空格要单独处理,入库前统一规范保证比对一致。


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

Nginx监听端口配置

Nginx监听端口配置

引言

listen是server块的第一行指令,决定Nginx在哪个端口接收请求。一个server监听一个端口加域名组合,多站点靠server_name区分,多端口靠listen叠加。配错端口是最常见的502和无法访问根源。

基础HTTP监听

server {
    listen 80;
    server_name example.com;
    root /var/www/site;
    index index.html;
}

监听80端口,匹配example.com域名的请求进这个站点。

HTTPS监听

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
}

443必须加ssl关键字,同时配证书和私钥路径。

监听多个端口

server {
    listen 80;
    listen 8080;
    server_name example.com;
}

一个server块写多行listen,80和8080都走同一套配置。

只监听指定IP

listen 127.0.0.1:8080;

只绑回环地址,外部访问不到,适合内部管理接口。

80跳转到443

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

单独一个80 server做永久跳转,把HTTP请求引导到HTTPS。

默认服务器

server {
    listen 80 default_server;
}

default_server指定未匹配域名时的兜底站点,防直接用IP访问暴露默认页。

注意事项

  • 端口冲突先ss -tlnp查占用,别盲目改;
  • 443漏写ssl会导致证书不生效;
  • 云服务器还要在安全组放行对应端口;
  • listen不带IP默认绑0.0.0.0全部网卡;
  • 改端口后先nginx -t再reload;
  • 301跳转的旧URL参数用$request_uri保留。

总结

listen 80配HTTP,listen 443 ssl配HTTPS并带证书,多行listen监听多端口,IP:端口限定绑定网卡,80 server用return 301跳HTTPS,default_server做域名兜底。改前ss查占用、nginx -t校验、安全组放行,绑定IP时注意内外网可达性,跳转用$host和$request_uri保住完整地址。


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

Git查看本地分支

Git查看本地分支

引言

多人协作久了本地会攒下一堆分支,切分支前先看清当前在哪个分支、有哪些本地分支、哪些已合并。git branch是基础命令,配合不同参数能看本地、远程、合并状态。

列出本地分支

git branch

列出所有本地分支,当前分支前面带*号高亮,一眼定位自己在哪条线上。

看当前所在分支

git branch --show-current

只输出当前分支名,写脚本时取这个最干净,不用从整行结果里截取。

列出远程分支

git branch -r

只列远程跟踪分支;想看本地加远程全部用-a。

查看最后一次提交

git branch -v

每个分支后面跟最近一次提交的短hash和说明,判断分支是否还在推进。

看已合并到当前分支的分支

git branch --merged
git branch --no-merged

merged列出已并入当前线、可安全删除的分支;no-merged是还没并入、删之前要小心的。

查分支对应提交

git branch -vv

额外显示本地分支跟踪的远程分支和 ahead/behind 数量,看本地是否落后远程。

注意事项

  • git branch只看本地,远程分支要先fetch刷新;
  • 带*的是当前分支,别在错误分支上提交;
  • --merged里的分支可放心删,no-merged删前确认有无保留价值;
  • 删除已合并分支用git branch -d,未合并强删用-D;
  • 分支命名按feature/修复类型规范,方便批量清理;
  • 定期删过期本地分支,避免列表臃肿。

总结

git branch列本地并标当前分支,--show-current只取当前名,-r看远程,-v看各分支最新提交,--merged和--no-merged判断可否安全删除,-vv看跟踪关系和领先落后。切分支前先确认当前线,远程信息先fetch,已合并分支用-d安全删,未合并慎删,定期清理过期本地分支保持仓库清爽。


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

Linux查看系统版本

Linux查看系统版本

引言

接手一台服务器,第一件事是搞清楚发行版是CentOS、Ubuntu还是Debian、内核多少位、版本号多少,因为包管理器、软件源、命令差异都跟发行版绑定。下面几组命令覆盖发行版、内核、架构三个维度。

看发行版版本(推荐)

cat /etc/os-release

现代发行版通用,输出NAME、VERSION、PRETTY_NAME,CentOS、Ubuntu、Debian都适用。

看CentOS具体版本

cat /etc/redhat-release
# 或
rpm -q centos-release

RHEL系专用,直接显示CentOS Linux 7/8等版本号。

看Ubuntu/Debian版本

lsb_release -a
# 或
cat /etc/debian_version

lsb_release显示Distributor和Release,没装时读debian_version兜底。

看内核版本

uname -r
# 完整信息
uname -a

-r看内核版本号,-a连架构、主机名、编译时间一起输出。

看CPU架构

arch
# 或
uname -m

x86_64是64位,aarch64是ARM,选安装包时必须对应。

看系统位数和硬件

getconf LONG_BIT

输出32或64,比uname更直接回答多少位。

注意事项

  • 发行版和内核是两回事,Ubuntu 22.04可以换内核;
  • 包管理器按发行版选:CentOS用yum/dnf,Ubuntu用apt;
  • 容器内cat /etc/os-release看到的是镜像版本,不是宿主机;
  • 老系统可能没有os-release,用redhat-release或issue兜底;
  • 生产升级内核前先uname -r记录当前版本便于回退;
  • 脚本里判断发行版读/etc/os-release的ID字段最稳。

总结

cat /etc/os-release看通用发行版,redhat-release看CentOS,lsb_release看Ubuntu,uname -r看内核,arch/uname -m看架构,getconf LONG_BIT看位数。先分清发行版和内核,包管理器按发行版选,容器内读到的是镜像版本,脚本判断优先解析os-release的ID字段,升级内核前记录当前版本方便回退。


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

Docker停止删除容器

Docker停止删除容器

引言

容器用多了会堆积一堆停止状态的旧实例,占磁盘、干扰排查。停止和删除是两步:先停再删,生产环境删容器前要确认数据已持久化,否则删除即丢失。

停止单个容器

docker stop 容器名或ID

发送SIGTERM优雅停止,等待默认10秒,超时再发SIGKILL强杀。

强制停止

docker kill 容器名或ID

直接SIGKILL,不给进程清理机会,只用于卡死不响应的容器。

删除已停止容器

docker rm 容器名或ID

只能删停止状态的容器,运行中需先stop或加-f。

强制删除运行中容器

docker rm -f 容器名或ID

一步停止并删除,慎用,等价于kill后rm。

批量清理

# 删除所有已停止容器
docker container prune

# 删除全部容器(含运行中,危险)
docker rm -f $(docker ps -aq)

prune只清停止的,相对安全;ps -aq配合rm -f会删光,生产禁止随手执行。

启动与重启

docker start 容器名
docker restart 容器名

停止的容器start可再次拉起,文件系统内改动仍在,只有删容器才清除。

注意事项

  • 删除容器前确认挂载卷和数据,容器内非挂载目录删除即丢;
  • 数据库容器先备份再删,别以为能找回来;
  • -f强制删除跳过优雅停止,可能丢未刷盘数据;
  • 批量命令先docker ps -aq列出确认,再删;
  • 加了--rm的容器退出自动删除,不留残骸;
  • 清理容器不影响镜像,镜像还在可随时重建。

总结

docker stop优雅停止,docker kill强杀,docker rm删已停容器,-f强制删运行中容器,container prune批量清停止实例。核心原则是先确认数据挂载和备份再删,生产慎用rm -f全量清理,执行批量命令前先列出ID核对,理解容器删除会清空非挂载层数据。


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

MySQL查看表索引

MySQL查看表索引

引言

排查慢查询、判断索引是否生效前,先要搞清楚一张表上到底建了哪些索引、每个索引包含哪些列、是不是唯一索引。SHOW INDEX和information_schema都能查,前者直观,后者适合脚本批量统计。

SHOW INDEX查看索引

SHOW INDEX FROM users;

列出users表全部索引,Key_name是索引名,Column_name是索引列,Seq_in_index是列在联合索引里的顺序,Non_unique为0表示唯一索引。

重点字段含义

  • Key_name:索引名,PRIMARY是主键;
  • Seq_in_index:联合索引中的列顺序,最左前缀从这里判断;
  • Column_name:该索引包含的列;
  • Non_unique:0唯一,1普通;
  • Sub_part:前缀索引长度;
  • Cardinality:基数,越接近总行数区分度越好。

建表语句里看索引

SHOW CREATE TABLE users\G

直接看完整建表语句,KEY和UNIQUE KEY就是索引定义,适合快速人工核对。

information_schema批量查

SELECT table_name, index_name, column_name, seq_in_index, non_unique
FROM information_schema.statistics
WHERE table_schema = 'mydb'
ORDER BY table_name, index_name, seq_in_index;

一次性导出整库所有表的索引结构,做索引巡检和冗余分析。

查某列是否被索引覆盖

SELECT DISTINCT index_name FROM information_schema.statistics
WHERE table_schema='mydb' AND table_name='users' AND column_name='phone';

判断phone列有没有索引,决定查询能否走索引。

注意事项

  • 联合索引顺序决定最左前缀,别只看有没有建;
  • Cardinality是估算值,会随ANALYZE TABLE刷新;
  • 前缀索引Sub_part只索引前N个字符;
  • 重复冗余索引用pt-duplicate-key-checker之类工具清理;
  • 索引不是越多越好,写多表要权衡插入成本;
  • 查完索引再配合EXPLAIN看实际是否命中。

总结

SHOW INDEX FROM表看索引明细,SHOW CREATE TABLE看定义,information_schema.statistics批量导出整库索引。重点关注索引名、列顺序、唯一性和基数,联合索引按最左前缀使用,建完索引务必用EXPLAIN验证是否真正命中,定期清理重复冗余索引,避免写放大。


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

PHP字符串反转方法

PHP字符串反转方法

引言

字符串反转是面试和笔试高频小题,业务里也常用于生成验证码、翻转密文顺序、做简单加密。PHP内置strrev一行搞定,但处理中文时要注意字节和字符的区别。

英文字符反转:strrev

$str = "hello world";
echo strrev($str); // dlrow olleh

strrev按字节反转,英文和数字没有问题,性能也最高。

中文字符串反转

strrev直接转中文会乱码,因为中文UTF-8占3个字节,按字节切会把一个字拆坏。需要按字符处理:

$str = "技术分享";
$len = mb_strlen($str, 'UTF-8');
$result = '';
for ($i = $len - 1; $i >= 0; $i--) {
    $result .= mb_substr($str, $i, 1, 'UTF-8');
}
echo $result; // 享分术技

用mb_strlen取字符数,mb_substr按字符回取,保证每个完整汉字不被拆开。

数组方式实现

$arr = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
echo implode('', array_reverse($arr));

u修饰符按UTF-8切字符成数组,array_reverse翻转后再拼回去。

随机打乱:str_shuffle

echo str_shuffle("abc123"); // 每次结果随机

注意str_shuffle同样按字节,中文打乱不适用,验证码里建议只放英文数字。

注意事项

  • strrev只适合单字节英文,中文必须走mb系列;
  • 生产验证码用str_shuffle后再截取长度,别用中文;
  • 反转不等于加密,明文可还原,别拿来做真正加密;
  • mb扩展缺失时先检查extension_loaded('mbstring');
  • 超大字符串循环回拼建议用数组收集后implode,减少字符串反复复制。

总结

英文用strrev一行反转,中文用mb_strlen加mb_substr按字符倒取,数组preg_split配合array_reverse也能实现,随机打乱用str_shuffle但仅限英文数字。记住字节反转和字符反转的区别,中文场景务必走mb扩展,大字符串用数组收集再拼接提升性能。


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

Nginx配置检查测试

Nginx配置检查测试

引言

改完Nginx配置直接reload最容易踩坑:语法错、证书路径错、端口被占会导致Nginx起不来甚至站点全挂。规范流程是先检查语法,通过后再平滑重载,出问题能快速回退。

语法检查

nginx -t

检查默认配置文件语法,输出syntax ok和test is successful才说明配置无误。

指定配置文件检查

nginx -t -c /etc/nginx/nginx.conf

自定义路径时用-c指定,避免检查错文件。

平滑重载

nginx -s reload

不中断现有连接重新加载配置,改动生效不宕机。

查看编译参数和版本

nginx -V

大写V看版本和编译模块,确认是否包含ssl、gzip、stream等模块。

打印生效配置

nginx -T

大写T会把所有include进来的配置打印出来,排查重复server或覆盖时用。

出错常见原因

  • 括号不配对、分号漏写;
  • 证书key路径不存在或权限600不对;
  • listen端口已被其他进程占用;
  • upstream后端地址写错;
  • include路径下的文件本身语法错。

注意事项

  • 永远先-t再reload,禁止跳过检查;
  • 改配置前备份原文件,出错可立刻回退;
  • -T输出很长,重定向到文件再查;
  • reload失败先看error.log定位行号;
  • 生产环境改完curl本机验证再放行;
  • 多台机器逐台灰度,不要一次全改。

总结

nginx -t检查语法,-c指定文件,-s reload平滑生效,-V看编译模块,-T打印完整配置。铁律是先检查通过再重载,改前备份、出错看error.log、本机curl验证后再上线,生产逐台灰度,避免一次改全量导致整站不可用。


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

MySQL查看数据库列表

MySQL查看数据库列表

引言

刚连上一台MySQL服务器,先要看实例上有哪些库、当前账号能访问哪些库。SHOW DATABASES是最基础的入口,配合information_schema可拿到库名、字符集、容量等完整信息。

列出所有数据库

SHOW DATABASES;

列出当前账号有权限看到的全部库,看不到的库是权限不足,不代表不存在。

查看当前所在库

SELECT DATABASE();

切换库之前先确认当前连接落在哪个库,避免写SQL操作错库。

切换数据库

USE mydb;

切库后后续不加库名的表操作都落在mydb。

查库字符集和排序规则

SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA;

拿到每个库的默认字符集,排查乱码时先看这里。

查看库占用空间

SELECT table_schema,
       ROUND(SUM(data_length+index_length)/1024/1024,2) AS size_mb
FROM information_schema.tables
GROUP BY table_schema
ORDER BY size_mb DESC;

按库汇总数据和索引大小,快速找出占空间大户。

注意事项

  • SHOW DATABASES只显示有权限的库,根账号才看全;
  • information_schema是系统库,不要往里写业务数据;
  • 新库建库时指定utf8mb4,避免emoji乱码;
  • 删库DROP DATABASE不可逆,先备份再操作;
  • 多实例用端口区分,别把实例和库混淆;
  • 脚本里用SELECT SCHEMA_NAME批量遍历,比SHOW好解析。

总结

SHOW DATABASES列库,SELECT DATABASE()看当前库,USE切库,information_schema.SCHEMATA查字符集,按table_schema聚合查容量。先确认当前库再写SQL,建库统一utf8mb4,删库前务必备份,脚本批量处理优先走information_schema。


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

PHP时间戳转日期

PHP时间戳转日期

引言

数据库里常存Unix时间戳(秒级整数),展示到页面要转成可读日期;反过来用户选的日期也要转成时间戳入库。date()和strtotime()是这对基础转换工具,理解格式符和时区才能避免差8小时。

时间戳转日期

$ts = 1760000000;
echo date('Y-m-d H:i:s', $ts);

date()第二个参数缺省就是当前时间time(),传指定时间戳则格式化该时刻。

当前时间戳

echo time();            // 秒级时间戳
echo microtime(true);   // 带微秒的浮点时间戳

日期字符串转时间戳

$ts = strtotime('2026-10-19 08:30:00');
echo $ts;

strtotime支持英文相对表达式:

strtotime('tomorrow');
strtotime('+1 week');
strtotime('next Monday');

DateTime面向对象写法

$dt = new DateTime('@' . $ts);
echo $dt->format('Y-m-d H:i:s');

$d = DateTime::createFromFormat('Y-m-d', '2026-10-19');
echo $d->getTimestamp();

复杂时区和格式解析用DateTime更稳。

常用格式符

  • Y四位年,m两位月,d两位日,H24小时,i分,s秒;
  • 带毫秒用v,星期用D/l,12小时制用g/a。

注意事项

  • 服务器时区不对会差8小时,先date_default_timezone_set('Asia/Shanghai');
  • date()对1970前或2038前后32位系统有边界问题;
  • strtotime解析失败返回false,要判空再用;
  • 毫秒时间戳除以1000再传给date();
  • 跨时区存储建议统一UTC,展示时再转本地;
  • MySQL存int(10)秒级,避免用字符串日期做比较。

总结

时间戳转日期用date('Y-m-d H:i:s', $ts),日期转时间戳用strtotime(),复杂解析用DateTime。先设好Asia/Shanghai时区,毫秒先除以1000,strtotime失败要判空。入库统一存秒级时间戳,展示层再格式化,比存字符串日期更好比较和索引。


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

Docker查看运行容器

Docker查看运行容器

引言

部署多个服务后,先要看清哪些容器在跑、状态如何、映射了哪些端口、跑了多久。docker ps系列命令是日常运维入口,配合格式化和过滤快速定位目标容器。

只看运行中容器

docker ps

输出容器ID、镜像、命令、创建时间、状态、端口映射、名称,默认只列正在运行的。

查看全部容器(含已停止)

docker ps -a

把Exited状态的容器也列出来,排查退出残留时用。

只看容器ID

docker ps -q

输出纯ID,配合批量停止删除。

自定义输出列

docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

只保留关心的列,脚本采集更清晰。

按镜像过滤

docker ps --filter ancestor=nginx:latest

列出指定镜像启动的容器。

查看容器资源占用

docker stats

实时看每个容器CPU、内存、网络IO,Ctrl+C退出。

注意事项

  • ps默认不含已停止容器,找退出容器用-a;
  • STATUS显示Up时长或Exited码,退出码非0要查日志;
  • PORTS列看清宿主端口到容器端口的映射;
  • 容器名重复会冲突,--name要唯一;
  • 配合docker inspect看单个容器完整配置;
  • 批量操作前先用-q列出ID确认范围。

总结

docker ps看运行中容器,-a含已停止,-q取ID批量处理,--format自定义列,--filter按镜像过滤,docker stats看实时资源。运维先列清单确认状态和端口,再决定停止、重启或排查,避免凭印象操作容器。


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

Nginx默认站点配置

Nginx默认站点配置

引言

直接用IP或未匹配到server_name的域名访问时,Nginx会落到默认站点。装好Nginx自带的欢迎页就是默认server,安全上建议改掉,避免暴露版本或被扫描利用。

默认站点如何确定

同一端口下,第一个server块、或带default_server标记的就是默认站点:

server {
    listen 80 default_server;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;
}

server_name用_占位,表示匹配任意未命中的主机名。

配置占位拒绝页

对外只暴露正式站点时,默认站点直接返回444或简单403:

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

444是Nginx特有,直接断开连接,不返回内容。

隐藏版本号

http {
    server_tokens off;
}

避免错误页暴露Nginx版本,减少针对性漏洞扫描。

自定义默认首页

server {
    listen 80 default_server;
    server_name _;
    root /var/www/default;
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
}

多端口默认

HTTPS同理,443也要配default_server:

server {
    listen 443 ssl default_server;
    server_name _;
    # 自签或占位证书,否则握手失败
    return 444;
}

注意事项

  • 一台机器同端口只能有一个default_server;
  • 不配默认站,未匹配域名会落到第一个server,可能串站点;
  • 改完nginx -t校验再reload;
  • 云主机IP直扫建议默认站返回444,不暴露首页;
  • 区分default_server和servername ,前者是优先级标记;
  • SSL默认站必须有证书,否则浏览器握手报错。

总结

默认站点由default_server或同端口首个server决定,用server_name _占位。安全做法是把默认站返回444拒绝、server_tokens off隐藏版本,443也要配默认拒绝站,避免IP直扫串到正式站点。改完务必-t校验再reload。


emer 发布于  2026-10-19 07:35 
'); })();