news 2026/9/18 20:41:50

JMeter实战:一万并发串联接口压测的完整思路与配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JMeter实战:一万并发串联接口压测的完整思路与配置

年前接到一个活动项目的压测任务,场景很典型:一万名用户同时去请求两个活动接口,而且这两个接口还是串联的——第二个接口必须用到第一个接口的返回结果。这类场景在互联网业务里太常见了,要么是"参与活动领奖→用奖品兑换权益",要么是"下单→支付",接口之间有先后依赖关系,压测时如果只测单个接口,根本没法反映真实业务链路的承载能力。

我做压测这些年,最深的体会是:技术难度往往不在工具操作上,而在场景建模和问题定位上。一万并发看着只是在线程组里填个10000,但真要跑起来,压测机本身、网络连接、服务端资源、接口关联、数据准备,任何一个环节掉链子,结果都没法看。这篇就把这次压测的完整思路、配置步骤、关联实现和排查过程捋一遍,给后续做类似活动接口压测的朋友一个可以直接抄的作业。

1. 压测前期:先把业务逻辑和场景模型理清楚

1.1 一个典型的活动业务串联场景拆解

这次要压的活动是典型的"两步走":用户可以参与活动,系统返回一个参与记录编号和对应的奖品兑换码;紧接着用户拿着这个记录编号和兑换码去进行奖品核销。两个接口分别对应两次HTTP请求,第二个请求的入参完全依赖第一个请求的响应数据。

我一般接到这种有串联关系的压测需求,第一步不是打开JMeter,而是先和开发把接口文档对清楚:

  • 第一个接口返回的JSON里,哪个字段是第二个接口需要的?这个字段在响应体的什么路径下?
  • 第二个接口需要的是query参数、header还是request body?这个决定了提取后的变量在什么地方引用。
  • 两个接口之间是否有超时限制?比如用户必须在几秒内完成兑换,这个会影响压测时两个请求之间要不要加固定延时。

这次的情况是,第一个接口返回类似这样:

{ "code": 0, "msg": "success", "data": { "recordId": "R20240501001", "prizeCode": "PRIZE888" } }

第二个接口在请求体里需要传recordId和prizeCode这两个字段。那整个压测链路就非常清晰了:第一个请求发出后,从响应里抠出这两个字段的值,作为第二个请求的入参。这就是JMeter里常说的"接口关联",实现方式不外乎JSON提取器、正则表达式提取器,复杂一点的会用BeanShell或JSR223脚本,但能简单就简单,后面细说。

1.2 一万并发该怎么理解(不是简单填个10000)

很多人一看到"一万名用户同时请求",脑子里的第一反应就是线程数填10000,然后把循环次数设为1,点下启动就完事了。这其实是新手最容易踩的坑。

首先要搞清楚业务上的"同时请求"到底是什么概念。用户不会真的在同一微秒内全部点按钮,而是有一个到达过程。所以压测时我们要模拟的不是瞬间到达,而是某个时间段内的稳态并发——即系统在持续承受一万在线用户不断发起请求的压力。JMeter里控制这个行为的关键参数,一个是线程数,另一个是Ramp-Up Period(启动延迟时间)。

Ramp-Up Period指的是所有线程在多长时间内全部启动完毕。如果设置60秒,那一万线程就是大约每秒启动167个线程(10000÷60)。这样做的好处是给服务端一个"预热"的过程,避免一开机就一万请求砸过去直接把服务打崩,根本测不出真实的瓶颈拐点。但如果业务场景就是要模拟极端瞬间冲击(比如整点秒杀),那Ramp-Up可以缩短到几秒,甚至填0表示立即并发,不过这种情况下压测机自身的调度压力也最大。

我的建议是:初次压测用阶梯加压,不要一上来就拉满一万。先500、1000、3000、5000、8000、10000这样分阶段跑,每个阶段观察响应时间和错误率的变化,找到性能拐点。这比一次性跑完拿到一份"全红"的报告要有意义得多。

1.3 压力模型与数据准备

一万个用户同时跑,每个用户如果都用相同的数据请求,很容易被服务端的缓存或者幂等校验干扰。比如第一万个用户请求参与的接口,结果发现和前一个用户拿到的是同一个活动名额,那接口内部可能直接返回"已被领取",错误率就会虚高。

所以在压测之前,必须把测试数据准备好。这次活动接口,用户维度上最核心的是用户ID和手机号。我让开发和测试环境导出了一万个真实脱敏用户数据,做成了CSV文件,每行一个用户,大概长这样:

uid,phone U10001,13800000001 U10002,13800000002 ...

在JMeter里用CSV Data Set Config参数化,让每个线程从文件里按顺序或随机取一条数据。这样做的好处是每个虚拟用户都有独立身份,更贴近真实场景,也能避免因为数据重复造成的业务逻辑误判。

这里有个细节要注意,CSV Data Set Config放在线程组下、HTTP请求之前,默认是每个线程取一行。如果想要随机分配用户,可以把"共享模式"改成"每次取样"或者用随机顺序。但如果是严格模拟"一个用户一次请求",顺序取更合理,因为后面还要统计每个用户的操作成功率。

2. JMeter核心配置:线程组、默认请求与连接参数

2.1 线程组这样配置才不会把压测机打垮

JMeter的每个线程在操作系统层面就是一个真正的线程,一万个线程意味着你要开出一万条并发执行流。这对压测机本身的CPU和内存是有硬性要求的,不是随便一台电脑就能扛住的。

我在压测前第一件事是调大JMeter的JVM堆内存。默认的JMeter启动脚本给的内存很小,跑几百线程还行,跑一万线程分分钟OutOfMemoryError。修改方式很简单,找到JMeter安装目录下的bin/jmeter.bat(Windows)或者jmeter(Linux),编辑文件里的堆内存参数:

# Windows: jmeter.bat set HEAP=-Xms6g -Xmx8g -XX:MaxMetaspaceSize=2g # Linux: 通过环境变量覆盖 export HEAP="-Xms6g -Xmx8g -XX:MaxMetaspaceSize=2g"

这里注意,-Xms和-Xmx最好是同一个值,一次性把内存申请到位,避免运行过程中频繁扩容触发GC停顿。如果压测机内存不太够,至少保证4G起步,因为除了线程栈空间,JMeter还需要保存每个请求的响应数据(如果开启了结果树监听的话)。

线程组的配置这次我用的参数是:

  • 线程数:10000
  • Ramp-Up Period:120秒(每秒约83个线程启动)
  • 循环次数:1次
  • 调度器:不勾选,压测时长通过实际脚本运行控制

循环次数我设置为1,原因是这次关注的是一万用户的完整操作成功率,而不是单个用户反复刷接口。如果要测长时间稳定性,可以把循环次数设大或者勾选"永远",配合调度器的持续时间使用。

跑一万线程时,压测机在非GUI模式下大约要消耗8G左右内存,CPU也会跑到多核满载。所以强烈建议用命令行模式执行压测,不要在GUI界面里挂着跑大并发,GUI模式本身就要消耗大量资源,还会因为界面刷新影响压测数据的稳定性。我是这样执行的:

jmeter -n -t activity_pressure_test.jmx -l result.jtl -e -o report/

-n表示非GUI模式,-t指定脚本,-l输出原始结果文件,-e和-o是生成HTML报告用的,压测完直接就能看到完整的图表分析。

2.2 HTTP请求默认值和超时参数

一万并发下,HTTP请求的配置越统一越好管理。我习惯在测试计划下先添加一个"HTTP请求默认值"(HTTP Request Defaults),把公共信息一次性配好:

  • 协议:http
  • 服务器名称或IP:被测环境地址
  • 端口号:8080
  • 内容编码:UTF-8
  • 连接超时:3000毫秒
  • 响应超时:10000毫秒

超时参数一定要配,而且不能配得太大。我之前见过有同事把响应超时设为0(表示无限等待),结果服务端出现局部故障时线程全部挂在等待上,后续请求越积越多,最后把压测机自己搞崩了。设置合理的超时能让线程快速释放,比如连接超时3秒、响应超时10秒,一旦服务端处理不过来,请求就会以超时错误的形式记录下来,错误率指标才能反映真实情况。

同时,在请求里需要加的公共头也通过"HTTP信息头管理器"统一设置,比如Content-Type: application/json、Authorization等。这里有一个很容易被忽略的点:如果活动接口需要登录态,JMeter怎么处理?一般有两种方案,一种是在压测前先跑一次登录接口,用正则或JSON提取器拿到token,再加到后面的请求头里;另一种是让开发在测试环境统一放开鉴权,或者提供一个万能测试token。我这次用的是第二种,开发给了一个测试环境的固定token,省去了登录步骤,压力更聚焦在活动接口自身。

2.3 阶梯加压:从1000慢慢爬到10000

直接在线程组里写死10000线程,虽然也能跑,但问题是一旦服务端在3000并发时就扛不住了,你得到的结果只有一堆超时,至于瓶颈到底在哪、什么时候开始恶化,完全看不出来。所以我日常压测更喜欢用阶梯加压,这次也不例外。

如果用JMeter自带的线程组,想实现阶梯效果有两个办法:一是做多个线程组,分别设置1000、3000、5000、8000、10000,然后用"启动时间"错开;二是直接装一个jmeter-plugins-manager,通过它安装Custom Thread Groups插件,里面有现成的"Stepping Thread Group"(逐步加压线程组)和"Ultimate Thread Group"(终极线程组)。

Ultimate Thread Group的配置方式很直观,可以一行一行地定义线程池,每一行都包含启动时间、线程数、持续时间、停机时间。我这次的配置大致是:

线程数延迟启动持续时间停机时间
10000秒60秒10秒
300060秒60秒10秒
5000120秒60秒10秒
8000180秒60秒10秒
10000240秒120秒20秒

这样跑下来的结果里,每个阶段的错误率和响应时间都分布在不同的时间窗口,对照聚合报告可以明显看到从5000并发开始,平均响应时间开始拉高,到8000时错误率上升,到10000时出现超时,瓶颈就一目了然了。

3. 接口关联实现:把第一个接口的返回值交给第二个接口

3.1 JSON提取器和正则提取器的选型

这是串联接口压测的核心环节。在JMeter里做接口关联,最常用的两种后置处理器就是"JSON提取器"(JSON Extractor)和"正则表达式提取器"(Regular Expression Extractor)。

到底用哪个,我的选择标准很简单:接口返回的是标准JSON结构,优先用JSON提取器,因为JSONPath的语义清晰、容错性好;如果接口返回的是非标准结构(比如夹杂着各种描述文本的HTML、或者JSON嵌在字符串里),或者响应体太大、结构不固定,那就退回用正则表达式提取器。

这次两个接口返回的都是标准JSON,我直接用的JSON提取器,配置简单,可读性也强。但正则提取器我也一并讲了,因为实际工作中总会碰到一些返回结构比较奇葩的接口,到时候就知道多会一种方案有多省事了。

3.2 具体配置步骤(含表达式写法)

在第一个HTTP请求(参与活动接口)上右键添加后置处理器,选择JSON提取器。这里我给两个字段各建一个JSON提取器,也可以在一个提取器里一次提取多个变量,后者更高效。

单个字段的提取

  • 变量名称:recordId
  • JSONPath表达式:$.data.recordId
  • 匹配编号:1
  • 默认值:NOTFOUND

再添加一个JSON提取器提取prizeCode,配置同上,JSONPath表达式改成$.data.prizeCode。

如果嫌多个提取器麻烦,JMeter也可以在一个JSON提取器里同时提取多个变量。在变量名称和JSONPath表达式两栏里用分号分隔,比如:

变量名称:recordId;prizeCode JSONPath表达式:$.data.recordId;$.data.prizeCode

这样一次请求就能同时拿到两个变量。注意两边的数量必须一一对应,写错了提取就会失败。我把"默认值"统一设置成一个容易发现的字符串,比如NOTFOUND,这样一旦提取失败,第二个接口的请求体里会出现明显的NOTFOUND字样,从请求日志或者结果树里一眼就能看到关联有没有生效。

如果是用正则表达式提取器,配置方式是这样的:

  • 变量名称:recordId
  • 正则表达式:"recordId":\s*"([^"]+)"
  • 模板:$1$
  • 匹配编号:1
  • 默认值:NOTFOUND

这里的正则含义是匹配"recordId":后面紧跟的引号字符串,括号里的部分就是我们要抓取的内容。需要注意正则里的转义,JMeter对双引号和反斜杠的处理有时候会让人头大,测试完多看一眼提取结果总没错。

3.3 关联的调试与验证

配置完提取器,先不要急着跑一万并发。我会加一个"调试取样器"(Debug Sampler)放在第二个请求之前,然后先跑1个线程,打开"查看结果树"观察提取到的变量值。

Debug Sampler会用JSR223脚本的形式把当前线程上下文里的变量全部打印出来,如果你看到这样的输出:

recordId=R20240501001 prizeCode=PRIZE888

说明关联成功,这时候再把Debug Sampler禁用掉,正式跑压测。注意Debug Sampler在正式压测时一定要禁用或删除,因为每跑一次线程它都要把所有变量打一遍,大量IO操作会把压测机的性能拖垮,影响结果准确性。

第二个接口的请求体里直接引用变量就行,请求体大概是这样的:

{ "uid": "${uid}", "recordId": "${recordId}", "prizeCode": "${prizeCode}" }

引用变量就是${变量名}这种格式,JMeter会在运行时自动替换成实际值。

这里有一个细节很关键:提取器必须挂在第一个HTTP请求下面,作为它的子节点,而且在取样器顺序上要排在第二个HTTP请求之前。JMeter线程组里是顺序执行的,第一个接口跑完,提取器立刻处理响应结果,把变量存到当前线程的变量池里,紧接着第二个接口才能拿到变量。如果你的提取器放错位置,第二个接口拿到的永远是NOTFOUND,因为线程跑到第二个接口的时候,提取器压根还没执行。

4. 参数化、断言与监听器:让压测数据真实可分析

4.1 CSV参数化模拟一万用户身份

前面提到过,这次压测准备了带一万个用户信息的CSV文件。在JMeter里做参数化的标准组件是CSV Data Set Config,具体配置如下:

  • 文件名:/path/to/users.csv
  • 文件编码:UTF-8
  • 变量名称:uid,phone
  • 分隔符:,
  • 是否允许带引号:False
  • 遇到文件结束符:继续循环 或 停止线程
  • 线程共享模式:当前线程组

"线程共享模式"这里有个坑值得说下。默认配置下CSV文件由所有线程共享读取,每个线程拿到的行是顺序错开的;如果选择"当前线程",每个线程会从头开始读文件,容易造成数据重复。我的建议是共享模式保持默认,然后在请求里只引用uid,phone用不到就不引用。不过CSV里多列数据并不会影响读取,因为JMeter是按变量名来索引的。

如果测试环境没有那么多真实用户,也可以用JSR223取样器动态生成随机数据来模拟,比如用${__Random(1,10000)}生成随机ID,或者用${__time(yyyyMMddHHmmss)}生成不重复的操作流水号。但要注意,这种方式生成的随机数据如果业务端有唯一性校验,很可能产生大量冲突,所以有真实账号池的时候优先用CSV,实在没有才用函数生成。

4.2 响应断言配置

压测时只看HTTP状态码是远远不够的。如果接口内部抛了业务异常,响应码可能还是200,但响应体里的code字段变成了非0。这时候如果没有断言,这些请求会被当成成功请求统计,错误率被严重低估,报告也就失去了参考价值。

我这次为两个接口分别添加了"响应断言"(Response Assertion),逻辑是:响应文本中包含指定业务成功标识。活动接口的成功标识是"code":0,所以断言配置为:

  • 响应字段:响应文本
  • 模式匹配规则:包含
  • 测试模式:"code":0

这样,只要接口返回值里包含code为0的字段,请求就算通过;如果业务失败,比如返回"活动已结束"或"库存不足",断言就会失败,错误率立刻体现出来。

还有一点建议,不要在正式压测时把"查看结果树"监听器挂在测试计划里。我在调试阶段会开着查看结果树确认关联和断言配置正确,但正式压测前一定把它禁用。原因很简单,结果树会把每个请求的完整报文写到内存里,一万并发下这个监听器自己就能把堆内存吃满,导致压测提前OOM。

4.3 监听器与指标解读

这次压测用了两种结果输出方式:一是"聚合报告"(Aggregate Report),直接看关键指标的汇总;二是生成HTML报告,方便后续和团队一起复盘。

聚合报告里我最关心的几个指标是:

  • Samples:总请求数。如果线程数是10000,每个线程发两个请求,Samples应该是20000左右,如果少太多说明有请求没发出去或者被压测机卡住了。
  • Error%:错误率。这里需要看是业务报错还是连接超时,结合断言结果判断。
  • Average:平均响应时间。活动接口一般要求P95小于1秒,平均响应时间不能定得太死,因为不同接口逻辑复杂程度不同。
  • Throughput:吞吐量,单位是请求数/秒,代表系统每秒能处理多少请求。

不过聚合报告也有个短板:它不直接给TP99这种分位数。如果想知道99%的请求在多少毫秒内完成,需要用"汇总报告"的插件版本比如"p99/p90"列,或者直接在HTML报告中查看响应时间百分位图。这次跑完我导出的HTML报告里能直接看到中位数、90%响应时间、99%响应时间,活动场景用户体感更接近这种分位数指标,而不是平均响应时间。

配套地,我还会在压测过程中同步监控服务端的CPU、内存、数据库连接池和慢SQL情况,因为接口响应变慢往往不是接口本身的问题,而是下游的数据库或者Redis扛不住了。只有把压测端的TPS指标和服务端的系统指标放在一起看,才能锁定真正的瓶颈。

5. 在压测现场:常遇问题与排查实录

5.1 压测机自身瓶颈排查

一万线程不是所有机器都能扛的。我第一次跑的时候,压测机是一台4核8G的云主机,结果跑到6000线程左右的时候,JMeter报了OutOfMemoryError,压测被迫中断。后来我把堆内存调大,再观察压测机的CPU,发现已经打满了,线程上下文切换严重,客户端本身成了瓶颈。

这个问题的解决方案是上JMeter分布式压测,也就是大家常说的agent模式。一台主控机(Controller)负责调度脚本,多台从机(Agent)负责执行实际压力。比如两台从机,每台跑5000线程,总压力还是10000,但每台机器的负担直接减半。

分布式压测的配置不算复杂,从机上启动jmeter-server,主控机的jmeter.properties里配置remote_hosts,把从机IP和端口(默认1099)加进去,然后脚本通过"远程启动"选项分发到各从机执行。但有几个细节必须注意:

  • CSV文件里的用户数据,每台从机都要有一份,且路径一致,否则从机读不到文件会直接报错。
  • 从机的系统时间尽量同步,否则时序数据会乱。
  • 主控机不产生压力,只做调度和结果汇聚,所以主控机的配置可以比从机低一些,但别低太多。

如果实在没有多台机器做分布式,也可以试着用JMeter的线程组配置做单机极限压测,但要做好心理准备,结果可能受限于客户端资源。我的判断标准是:单机压到CPU超过80%,测出来的数据就不太可信了,优先考虑扩展从机。

5.2 服务端性能瓶颈与连接数问题

去掉压测机自身问题后,最常遇到的另一类问题是服务端连接耗尽。有一次压测过程中,服务端的Tomcat直接拒绝连接,错误日志里大把的"Connection refused"。排查了一下,Tomcat默认的maxThreads是200,数据库连接池默认配置也不大,一万并发远超它本身能承受的连接数上限。

这种问题从压测的角度不能算是测试脚本的问题,但对压测执行者来说,必须能判断出来并推动服务端调优。常见的调优方向包括:

  • 调整Web容器线程池大小:比如Tomcat的maxThreads调整为2000,并同步调整acceptCount。
  • 调整数据库连接池:Druid或HikariCP的最大连接数要匹配实际并发,避免连接池排队。
  • 开启连接复用:HTTP请求头里加上Connection: keep-alive,减少TCP握手开销。
  • 调整操作系统层面参数:Linux上服务端需要适当调大文件描述符,默认1024肯定不够,至少要设到65535,压测机上也要避免TIME_WAIT端口堆积。

说到TIME_WAIT,压测机上如果大量出现"java.io.ioexception: error writing to server"这种报错,基本上就是TCP连接建立不过去了。排查方法是压测中在压测机上执行netstat -ant | grep TIME_WAIT | wc -l,如果数量达到几万,说明本地临时端口号已经排光了。Linux系统可以这样调整:

# 调整临时端口范围 sysctl -w net.ipv4.ip_local_port_range="1024 65535" # 开启连接复用 sysctl -w net.ipv4.tcp_tw_reuse=1

注意这些是压测机上的优化,生产服务器或者被测环境不建议随意开启tw_reuse,有可能引入连接数据混乱的问题。

5.3 关联失效的几类原因

接口关联这块最容易出的问题有三类,这里整理成一个速查表方便排查:

现象可能原因排查方法
第二个接口请求体里全是NOTFOUNDJSON提取器的JSONPath表达式写错先用单线程+查看结果树观察提取结果
变量没提取到但正则表达式看着没问题响应体过大,正则匹配到第一个就停了检查匹配编号和贪婪匹配写法
一部分用户成功、一部分失败业务数据差异导致响应结构不同打开结果树对比成功和失败响应体
断言报错但接口返回正常响应文本里字符串格式不一致,比如code和0之间有空格用"包含"模式而非"等于",放宽模式匹配规则
分布式从机上关联全部失败CSV文件路径或编码不一致确认每台从机的文件实际存在且内容一致

还有一个非常隐蔽的问题:如果两个接口在同一个线程组里,但JMeter的循环次数大于1,提取器会反复执行并把变量覆盖。如果第一个接口的返回结果不是每次都是新数据,第二个接口用到的可能是上一轮的旧值。所以串联接口的压测场景,循环次数通常设为1,或者确保每次循环的数据互不干扰。这个踩过一次,印象特别深。

5.4 分布式压测的规划

最后说说这次一万并发在分布式压测下的分配方案。活动接口的单请求处理时间大约在几十毫秒到几百毫秒之间,单机跑一万线程至少需要16G内存和8核CPU。我这次是用3台从机来分担:每台从机分到3333个线程,主控机负责统一的脚本分发和结果汇总。

不过需要提醒的是,在分布式模式下,每个从机都在输出自己的压测结果,最后汇聚到主控端时,主控端的磁盘IO可能成为瓶颈。所以我一般让每台从机把结果写到本地,压测结束后再用脚本汇总成一份完整的聚合报告,而不是全部实时传回主控机。小技巧,但在大规模压测时很好用。

从性能分析的角度,这次压测结束后我拿到了几个关键数据:系统的最大TPS大概是多少、在哪个并发量级开始劣化、错误率是否有突刺、两个接口之间的吞吐量差异有多大。这些数据最终汇成一份压测报告,给团队和运维做容量评估参考,后面加机器扩节点也有数据支撑。

小结之外的几句实操体会

这类串联接口的高并发压测,我做了不止一次,最想提醒后来人的是三件事:第一,关联提取器一定要先小规模调通,不要拿一万线程去赌提取表达式有没有写对;第二,压测机资源要提前评估,内存、端口、文件描述符这些基础环境配置比测试脚本更早暴露问题;第三,任何一次压测都要保留原始结果文件,别压完就删,后面复盘和对比调优全靠它。

最后再分享一个细节:跑完压测后不要只盯着平均值和错误率,多看一眼响应时间的90%、99%分位,活动接口的用户体感往往取决于最差的那一批请求能不能扛住。把P99压下去,比单纯把平均值做低,对业务的意义要大得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 20:40:07

课程论文交付前清单:用书霸AI写作

www.shubaai.com课程论文最容易出现的问题,不一定是“不会写”,而是写作过程中缺少一套稳定的检查流程。题目范围过大、文献与观点脱节、格式前后不统一,往往会让一篇本来有想法的论文失去完整性。下面这份清单,可以作为课程论文写…

作者头像 李华
网站建设 2026/9/18 20:39:51

Altium Designer 18网络颜色功能全解析:从设置到应用,避免PCB设计返工

1. 网络颜色不是花架子:一次电源返工让我重新认识它在开始讲具体操作之前,我想先聊一段实际经历。上个季度做一块四层电源板,PCB Layout完成得很快,结果样机回来后调试,发现3.3V网络和GND之间阻抗只有几十欧&#xff0…

作者头像 李华
网站建设 2026/9/18 20:39:05

ISO14001:2015中文版PDF条款结构化与合规检索

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 20:37:38

仓库标准怎么读,TaoToken 让 Agent 先核对 public-apis 文档

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 20:36:36

AI写真保姆级教程:从扩散模型到数字分身,一键生成大片

这两天我的朋友圈和几个短视频平台都被同一件事刷屏了——AI写真。不只是年轻人玩,连我那几个十几年没拍过正式照片的长辈,都上传了二三十张自拍,生成了一组看不出年龄的职业照和古风写真。如果你还没用过这类免费的AI写真神器,那…

作者头像 李华