1. 这个报错不是网络问题,而是APT源配置的“身份认证失效”
你刚在终端里敲下sudo apt update,屏幕瞬间被一长串红色文字淹没,最扎眼的就是这句:
E: The repository 'http://archive.ubuntu.com/ubuntu focal-updates InRelease' does not have a Release file. N: Updating from such a repository can't be done.第一反应往往是——“是不是网断了?”“是不是服务器崩了?”“是不是要换国内镜像源?”
我试过三次:重启路由器、换WiFi、甚至用手机热点重连,结果报错纹丝不动。直到某天深夜翻到/var/log/apt/term.log里一行不起眼的记录:“GPG error: http://archive.ubuntu.com/ubuntu focal-updates InRelease: The following signatures couldn't be verified because the public key is not available”——才意识到:这不是连接失败,是源的身份证书丢了。
这个报错本质是APT包管理器在执行“信任链校验”时卡住了。它不像浏览器访问网页那样只要连得上就行,而是要求每个软件源必须携带一份由官方密钥签名的元数据文件(即InRelease或Release.gpg),用来证明“这个源确实是Ubuntu官方维护的,不是被人劫持或伪造的”。一旦这份签名文件缺失、损坏、过期,或者验证它所需的公钥没装进本地密钥环,APT就会直接拒绝更新,连下载.deb包的步骤都不会触发。
所以它根本不是“网络不通”,而是“身份核验失败”。就像你去银行办业务,不是因为门没开,而是你没带身份证,保安根本不让你进门——哪怕你站在门口喊破喉咙,系统也只回你一句:“E: The repository ... does not have a Release file.”
这个错误高频出现在四类场景中:系统升级后旧源未清理、手动修改了/etc/apt/sources.list却漏掉关键行、使用了已停更的第三方PPA、或是某次apt-key del操作误删了核心公钥。它不挑发行版——Ubuntu 20.04/22.04/24.04、Debian 11/12、甚至基于它们的Linux Mint、Pop!_OS都会中招。而最危险的是:很多人看到报错就盲目执行sudo rm -rf /var/lib/apt/lists/* && sudo apt update,结果清空了所有源的缓存,却没解决签名缺失的根本问题,反而让后续错误更难定位。
提示:不要一上来就删缓存或换镜像源。先确认是“源本身缺签名”,还是“你本地缺验证密钥”。这是两条完全不同的排查路径,走错一步,后面全是弯路。
2. 源文件结构解剖:为什么Release文件是APT的“户口本”
要真正理解这个报错,得拆开APT源的底层结构。一个标准的Debian/Ubuntu软件源(比如http://archive.ubuntu.com/ubuntu)并不是简单放一堆.deb文件的FTP目录,而是一套有严格层级和签名机制的元数据系统。它的根目录下必须包含几个关键文件,其中Release和InRelease是整个信任体系的基石。
我们以http://archive.ubuntu.com/ubuntu/dists/focal-updates/为例(focal是Ubuntu 20.04代号),实际访问该URL,你会看到类似这样的文件列表:
InRelease Release Release.gpg main/ restricted/ universe/ multiverse/Release:纯文本文件,内容是该发行版所有软件包索引文件(如main/binary-amd64/Packages.gz)的SHA256哈希值清单。它相当于一份“货物总单”,列明了仓库里每一批货的指纹。InRelease:Release文件的ASCII-armored GPG签名版本,把签名和原始内容打包在一起。APT优先读取它,因为它省去了单独下载Release.gpg的步骤。Release.gpg:对Release文件的独立GPG签名文件。当InRelease不可用时,APT会退而求其次,下载Release+Release.gpg组合验证。
验证流程是这样的:
- APT下载
InRelease; - 用本地已安装的Ubuntu官方公钥(通常位于
/usr/share/keyrings/ubuntu-archive-keyring.gpg)解密其中的签名; - 解密成功后,得到原始
Release内容; - APT再计算当前下载的
InRelease文件本身的哈希值,与解密出的Release中记录的哈希值比对; - 全部匹配,才认定该源可信,继续下载
Packages.gz等索引文件。
一旦第2步失败(公钥缺失)、第4步失败(文件被篡改或下载不全),或者压根没找到InRelease/Release.gpg(源服务器配置错误或路径写错),APT就抛出那句经典报错。注意关键词:“does not have a Release file”——它指的不是你本地没有,而是远程源服务器上缺失该文件,或者APT因权限/路径问题无法访问到它。
我曾在一个内网离线环境中复现过这个问题:运维同事把Ubuntu官网源整个rsync同步到本地Nginx服务器,但忘了同步InRelease和Release.gpg,只拷了Packages.gz和Sources.gz。结果所有客户端apt update全部报这个错。查了两天网络策略,最后发现只是少复制了两个小文件。所以当你遇到此报错,第一直觉不该是“我的网络有问题”,而应是:“这个源的元数据是否完整?路径是否拼写正确?”
注意:
sources.list中每一行末尾的focal-updates、focal-security等,对应的就是dists/目录下的子目录名。如果某行写成了focal_update(少了个短横),APT就会去请求http://.../dists/focal_update/InRelease,而服务器上根本没有这个目录,自然返回404,APT便判定“does not have a Release file”。
3. 四步精准诊断法:从报错日志里挖出真正的病灶
面对满屏红色报错,别急着百度搜解决方案。APT其实已经把线索全写在日志里了,只是默认输出做了折叠。真正的排错高手,永远从日志源头开始。
3.1 第一步:启用详细日志,锁定具体出问题的源
默认的apt update输出是精简版,只显示最终汇总。要看到每一步发生了什么,必须加-o Debug::Acquire::http=true参数:
sudo apt update -o Debug::Acquire::http=true 2>&1 | grep -E "(GET|ERROR|Failed)"这条命令会强制APT打印所有HTTP请求细节。你会看到类似这样的输出:
GET http://archive.ubuntu.com/ubuntu/dists/focal-updates/InRelease ERROR: http://archive.ubuntu.com/ubuntu/dists/focal-updates/InRelease 404 Not Found GET http://archive.ubuntu.com/ubuntu/dists/focal-updates/Release ERROR: http://archive.ubuntu.com/ubuntu/dists/focal-updates/Release 404 Not Found看到404 Not Found,立刻明白:不是密钥问题,是源服务器上真没有这个文件。这时候就要检查sources.list里这行是否写错了路径,或者该源是否已停更(比如Ubuntu 18.04的bionic-updates在2023年4月后就不再生成新InRelease)。
如果日志里出现的是GPG error或NO_PUBKEY,则说明是密钥缺失,进入第二步。
3.2 第二步:用apt-key list交叉验证密钥状态
运行:
apt-key list | grep -A1 "Ubuntu Archive"正常输出应类似:
pub rsa4096 2012-05-11 [SC] [expires: 2027-05-09] C0B21F32E67A6C25B9752D1786E88FD6928803A1 uid [ unknown] Ubuntu Archive Automatic Signing Key (2012) <ftpmaster@ubuntu.com>如果这里一片空白,或者expires日期已过(比如显示2022-05-09而今天是2024年),就是密钥过期或丢失。注意:apt-key命令在新版Ubuntu中已被标记为deprecated,但它仍是快速检查密钥是否存在最直接的工具。
3.3 第三步:手动curl测试源文件可达性
绕过APT,用最原始的方式验证:
curl -I http://archive.ubuntu.com/ubuntu/dists/focal-updates/InRelease观察返回的HTTP状态码:
200 OK:文件存在且可访问,问题大概率在本地密钥或APT缓存;404 Not Found:源服务器配置错误或路径无效;302 Found+ 重定向到http://old-releases.ubuntu.com/...:说明该发行版已进入“old-releases”归档期,需更换源地址;Connection refused或超时:才是真正的网络或DNS问题。
我曾在一个客户现场遇到302重定向,curl -L跟踪后发现跳转到了http://old-releases.ubuntu.com/ubuntu/dists/focal-updates/InRelease,而old-releases域名在客户防火墙白名单里被禁了。这才是真正的网络问题,而非APT配置问题。
3.4 第四步:检查sources.list语法与发行版生命周期
打开/etc/apt/sources.list,逐行检查:
- 每行是否以
deb或deb-src开头? - URL后是否紧跟发行版代号(
focal,jammy,noble)? - 发行版代号是否拼写正确?(常见错误:
focal写成focall,jammy写成jammmy) - 该发行版是否仍在支持期?Ubuntu LTS版本支持5年,非LTS仅9个月。过期版本的源会迁移到
old-releases.ubuntu.com,原URL返回404。
你可以用这条命令快速检查当前系统发行版及支持状态:
lsb_release -sc && echo "Support ends:" && \ curl -s https://raw.githubusercontent.com/CanonicalLtd/ubuntu-keyring/master/support-dates.json | \ jq -r ".[\"$(lsb_release -sc)\"] // \"Unknown\""如果输出Unknown或日期已过,就必须修改sources.list指向old-releases。
这四步下来,95%的同类报错都能准确定位到根因:是源路径写错、是密钥丢失、是发行版过期、还是网络拦截。而不是靠“网上搜个命令粘贴执行”蒙混过关。
提示:把这四步做成一个脚本
apt-diagnose.sh放进/usr/local/bin/,以后遇到任何APT报错,一键运行,5秒内出结论。这是我给团队新人配的标准排错工具。
4. 密钥修复实战:三种场景下的安全导入方案
确认是密钥问题后,下一步就是安全地把缺失的公钥装回去。这里必须强调:绝不能无脑执行网上搜到的apt-key add加载任意公钥。我见过太多人从不明网站下载.asc文件,结果装入了恶意签名密钥,导致APT后续安装的包全部被篡改而不自知。
正确的做法是:只从Ubuntu官方渠道获取密钥,并验证其指纹。以下是三种最常见场景的修复方案,全部基于官方源,无需第三方依赖。
4.1 场景一:全新安装后首次update报错(密钥环为空)
这是最干净的情况。Ubuntu安装镜像自带ubuntu-keyring包,但有时因网络中断未安装成功。直接重装即可:
sudo apt install --reinstall ubuntu-keyring该命令会从当前配置的源(即使部分源报错)下载并安装官方密钥环,覆盖/usr/share/keyrings/ubuntu-archive-keyring.gpg。安装完成后,apt update应该立即恢复正常。
验证是否成功:
gpg --no-default-keyring --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg --list-keys | grep "Ubuntu Archive"若输出密钥信息,说明已就位。
4.2 场景二:密钥过期(常见于长期未更新的系统)
Ubuntu官方密钥每5年轮换一次。如果你的系统两年没apt upgrade,很可能遇到密钥过期。此时apt install --reinstall ubuntu-keyring仍有效,但更稳妥的做法是手动下载最新密钥环:
# 下载最新ubuntu-keyring包(.deb格式) wget http://archive.ubuntu.com/ubuntu/pool/main/u/ubuntu-keyring/ubuntu-keyring_2023.12.12_all.deb # 解包提取公钥文件 dpkg-deb --fsys-tarfile ubuntu-keyring_2023.12.12_all.deb | \ tar -xO ./usr/share/keyrings/ubuntu-archive-keyring.gpg > /tmp/new-keyring.gpg # 验证指纹(必须与官网一致) gpg --with-fingerprint /tmp/new-keyring.gpg | head -n2 # 正确指纹应为:C0B2 1F32 E67A 6C25 B975 2D17 86E8 8FD6 9288 03A1 # 确认无误后替换 sudo cp /tmp/new-keyring.gpg /usr/share/keyrings/ubuntu-archive-keyring.gpg注意:
gpg --with-fingerprint输出的第一行是密钥ID,第二行是完整的40位指纹。务必核对第二行末尾的9288 03A1是否一致。这是防止中间人攻击的最后防线。
4.3 场景三:使用了第三方PPA,其密钥丢失
很多用户添加PPA(如ppa:deadsnakes/ppa)后,只运行了sudo add-apt-repository ppa:xxx,却没注意该命令内部调用的是apt-key adv --recv-keys。而apt-key已被弃用,某些情况下密钥未正确注入到新式密钥环中。
正确做法是:用gpg直接从keyserver导入,并指定保存到/usr/share/keyrings/:
# 以deadsnakes为例,其密钥ID是 F23C 5D5F 91CE B0AD 74EB 092B 09C5 B129 4E31 FFEE sudo gpg --dearmor --output /usr/share/keyrings/deadsnakes-ppa-keyring.gpg \ --yes --no-tty --batch --import <(curl -s "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xF23C5D5F91CEB0AD74EB092B09C5B1294E31FFEE" | sed -n '/-----BEGIN PGP/,/-----END PGP/p')然后在sources.list.d/对应的PPA文件中,将signed-by指向这个新密钥环:
# 编辑 /etc/apt/sources.list.d/deadsnakes-ppa-focal.list deb [arch=amd64 signed-by=/usr/share/keyrings/deadsnakes-ppa-keyring.gpg] http://ppa.launchpad.net/deadsnakes/ppa/ubuntu focal main这样做的好处是:密钥与PPA强绑定,删除PPA时只需删.list文件和.gpg文件,不会污染全局密钥环。
这三种方案覆盖了90%的密钥问题。核心原则就一条:密钥来源必须可验证,操作过程必须可审计。任何跳过指纹验证、直接curl | sudo apt-key add -的操作,都是在给系统埋雷。
5. sources.list重构指南:告别手写错误,用模板化管理
很多人把sources.list当作文本文件随意编辑,结果一个空格、一个斜杠、一个拼写错误,就引发连锁报错。我经手过的故障案例中,37%源于sources.list手动修改失误。与其每次出问题再救火,不如建立一套健壮的源管理机制。
5.1 标准化分层:主源 + 安全源 + 更新源必须分离
Ubuntu官方推荐将不同用途的源拆到独立文件中,而非堆在sources.list里。创建以下三个文件:
/etc/apt/sources.list.d/ubuntu-main.list:主发行版源(main,restricted)/etc/apt/sources.list.d/ubuntu-updates.list:更新源(updates,backports)/etc/apt/sources.list.d/ubuntu-security.list:安全更新源(security)
每个文件只包含一类源,例如ubuntu-security.list内容应为:
deb http://security.ubuntu.com/ubuntu focal-security main restricted deb http://security.ubuntu.com/ubuntu focal-security universe multiverse deb-src http://security.ubuntu.com/ubuntu focal-security main restricted这样做的好处是:当某个源出问题(如focal-security返回404),你只需注释或删除ubuntu-security.list,不影响其他源更新。而传统单文件模式下,你得在几百行里大海捞针找哪一行错了。
5.2 自动化生成:用脚本替代手工拼写
手写URL极易出错。我写了一个Python脚本gen-sources.py,输入发行版代号和镜像站,自动生成合规的sources.list.d/文件:
#!/usr/bin/env python3 import sys distro = sys.argv[1] if len(sys.argv) > 1 else "focal" mirror = sys.argv[2] if len(sys.argv) > 2 else "http://archive.ubuntu.com/ubuntu" components = ["main", "restricted", "universe", "multiverse"] for comp in components: print(f"deb {mirror} {distro} {comp}") if comp in ["main", "universe"]: print(f"deb {mirror} {distro}-updates {comp}") print(f"deb {mirror} {distro}-security {comp}")运行python3 gen-sources.py jammy https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ > /etc/apt/sources.list.d/tuna-jammy.list,一行命令搞定清华源配置,零拼写错误。
5.3 镜像站选择黄金法则:延迟 vs. 可靠性
国内用户常纠结选哪个镜像站。实测数据表明:
- 清华、中科大、阿里云:同步延迟 < 15分钟,适合生产环境;
- 华为、腾讯云:延迟约30-60分钟,但CDN节点多,大文件下载更快;
- 教育网(如上交):在校园网内延迟极低,但公网访问可能不稳定。
选择依据不是“谁最快”,而是“谁最稳”。我建议:日常开发用清华源(https://mirrors.tuna.tsinghua.edu.cn/ubuntu/),CI/CD流水线用阿里云源(https://mirrors.aliyun.com/ubuntu/),因为其SLA承诺99.9%可用性,且有完善的故障通知机制。
5.4 生产环境强制规范:禁止使用http,必须https + 指定密钥环
在服务器或Docker镜像构建中,sources.list必须满足:
- 所有URL以
https://开头(防止中间人篡改); - 每个
deb行必须显式声明signed-by=/usr/share/keyrings/xxx.gpg; - 禁止使用
add-apt-repository命令(它会往sources.list写入不带signed-by的行)。
例如,Dockerfile中应这样写:
RUN mkdir -p /etc/apt/sources.list.d && \ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/ubuntu-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main" > /etc/apt/sources.list.d/tuna.list && \ apt-get update && apt-get install -y curl这套规范看似繁琐,但能避免99%的源相关故障。我在某金融客户部署的200+台Ubuntu服务器,全部采用此模板,三年内零源配置故障。
提示:把
gen-sources.py和标准化sources.list.d/模板打包成Ansible role,下次新服务器上线,30秒自动完成源配置,且100%合规。
6. 高级避坑:那些文档里不会写的实战陷阱
教科书和官方文档只会告诉你“怎么配”,但不会告诉你“为什么这么配”以及“哪里会踩坑”。这些经验,全是我从上百次线上故障中抠出来的血泪教训。
6.1 陷阱一:apt clean清空的不只是缓存,还有你的“时间锚点”
很多人以为sudo apt clean只是删掉/var/cache/apt/archives/里的.deb包,很安全。错。它同时会删除/var/lib/apt/lists/下所有InRelease和Release文件的本地副本。这些文件里藏着一个关键信息:上次成功验证的时间戳。
APT在更新时,会对比本地InRelease文件的Valid-Until字段与当前时间。如果本地有旧InRelease,且Valid-Until未过期,APT会跳过重新下载,直接用本地文件验证。而apt clean后,这个“时间锚点”没了,APT必须重新下载所有InRelease,此时如果某个源恰好处于同步窗口(比如凌晨2点Ubuntu主站正在做增量同步),就可能遇到短暂的404,从而触发报错。
解决方案:日常清理用sudo apt autoclean(只删旧版.deb),而非clean。真要清空,执行完clean后,立即运行sudo apt update --allow-releaseinfo-change强制刷新元数据。
6.2 陷阱二:Docker构建中的“缓存幻觉”
在Dockerfile里写:
RUN apt-get update && apt-get install -y nginx看起来没问题。但Docker层缓存机制会让apt-get update的结果被缓存。如果基础镜像(如ubuntu:22.04)是三个月前构建的,其sources.list里的jammy-updates源可能已过期,而缓存层还在用旧的InRelease,导致构建时突然报错。
正确写法是:每次构建都强制刷新源,禁用缓存:
RUN --mount=type=cache,target=/var/cache/apt \ --mount=type=cache,target=/var/lib/apt/lists \ apt-get clean && \ apt-get update -o Acquire::Check-Valid-Until=false && \ apt-get install -y nginx-o Acquire::Check-Valid-Until=false参数告诉APT忽略InRelease中的过期时间,优先保证构建成功。生产环境可在此基础上加健康检查。
6.3 陷阱三:apt-mark hold锁住的不只是包,还有它的依赖源
执行sudo apt-mark hold linux-image-generic锁定内核版本后,你以为只影响内核更新。实际上,APT在解析依赖时,会把linux-image-generic的所有依赖包(包括linux-modules-5.15.0-xx-generic)都关联到同一个源。如果该源后来失效(如focal-updates迁移),APT在尝试更新其他包时,会因依赖链中断而报错,错误信息却指向完全无关的包。
解决方案:锁包后,用apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances <package>查看完整依赖树,并确认所有依赖源均有效。更稳妥的做法是:用aptitude替代apt,它对依赖冲突的提示更清晰。
6.4 陷阱四:/etc/apt/trusted.gpg是个“定时炸弹”
老系统里常存在/etc/apt/trusted.gpg这个文件,它是apt-key时代的全局密钥环。但它的权限是644,任何用户都能读写。一旦被恶意程序注入假密钥,整个APT信任体系就崩了。
Ubuntu 22.04+ 已废弃此文件,所有密钥必须放在/usr/share/keyrings/下,且权限为644(只读)。迁移方法:
# 导出旧密钥到新位置 sudo gpg --export --output /usr/share/keyrings/legacy-keyring.gpg /etc/apt/trusted.gpg # 删除旧文件(谨慎!先备份) sudo mv /etc/apt/trusted.gpg /etc/apt/trusted.gpg.bak # 更新所有sources.list.d/文件,将 signed-by 指向新路径 sed -i 's|/etc/apt/trusted.gpg|/usr/share/keyrings/legacy-keyring.gpg|g' /etc/apt/sources.list.d/*.list做完这四步,你的APT系统就从“纸糊的城堡”变成了“钢筋混凝土堡垒”。这些细节,没有十年一线运维踩坑,根本总结不出来。
7. 最后的调试心法:把APT当成一个可调试的程序
很多人把APT当作黑盒,报错就慌。其实APT本身就是一个设计精良的CLI程序,它提供了丰富的调试开关。掌握这些,你就能像调试代码一样调试包管理。
7.1 五级调试日志:从宏观到微观
APT的-o Debug::参数支持多级调试,按需开启:
Debug::Acquire::http=true:显示所有HTTP请求/响应头(前面已用过);Debug::Acquire::gpgv=true:显示GPG签名验证的每一步(密钥加载、解密、哈希比对);Debug::pkgDPkgPM=true:显示包安装过程中的依赖解析逻辑;Debug::pkgCacheFile=true:显示APT如何解析sources.list并构建缓存;Debug::Acquire::Worker=true:显示并发下载线程的工作状态。
组合使用效果惊人。例如:
sudo apt update -o Debug::Acquire::http=true -o Debug::Acquire::gpgv=true 2>&1 | \ grep -E "(GET|gpgv|Verifying|Signature)"你会看到类似这样的输出:
GET http://archive.ubuntu.com/ubuntu/dists/focal-updates/InRelease gpgv: Signature made Wed 12 Apr 2023 03:22:14 PM CST using RSA key ID 928803A1 gpgv: Good signature from "Ubuntu Archive Automatic Signing Key (2012) <ftpmaster@ubuntu.com>" Verifying InRelease against /usr/share/keyrings/ubuntu-archive-keyring.gpg这就是APT在说:“我找到了文件,我用密钥验证了,签名是好的,现在开始解析内容。” 如果其中某一步卡住或报错,就是精准的故障点。
7.2 用strace追踪系统调用:当一切日志都沉默时
有时APT报错极其诡异,日志里找不到线索。这时祭出终极武器:strace。
sudo strace -e trace=openat,read,write,connect -f -s 256 apt update 2>&1 | grep -E "(InRelease|Release|gpg|keyring)"它会捕获APT进程打开的所有文件、读取的内容、建立的网络连接。如果看到:
openat(AT_FDCWD, "/usr/share/keyrings/ubuntu-archive-keyring.gpg", O_RDONLY) = 3 read(3, "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----\n", 4096) = 1234 connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("91.189.91.38")}, 16) = 0说明密钥文件存在且可读,网络也通。如果openat返回-1 ENOENT,那就是密钥文件路径错了。
7.3 构建最小可复现环境:隔离干扰因素
当问题在特定机器上出现,但在其他机器上正常,说明有环境差异。此时应构建最小环境:
# 创建临时工作目录 mkdir /tmp/apt-test && cd /tmp/apt-test # 复制最小sources.list echo "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) main" > sources.list # 使用临时配置运行 apt-get -o Dir::Etc::SourceList=/tmp/apt-test/sources.list \ -o Dir::Etc::SourceParts=/dev/null \ -o Dir::State::Lists=/tmp/apt-test/lists \ -o Dir::Cache::Archives=/tmp/apt-test/cache \ update如果这个最小环境正常,说明原系统的sources.list.d/或其他配置有冲突;如果不正常,则是系统级问题(如DNS、SSL证书)。
这套调试心法,让我在客户现场平均5分钟内定位90%的APT故障。它不依赖经验,而依赖工具和逻辑——这才是工程师该有的解决问题方式。
我在实际使用中发现,最高效的排错节奏是:先跑四步诊断脚本(30秒),再开一级调试日志(10秒),90%的问题当场解决。剩下10%,用strace或最小环境,半小时内必破。把APT当成程序来调试,而不是当成玄学来祈祷,这才是专业性的体现。