news 2026/9/11 4:49:18

PostgREST 可观测性完全指南:日志、指标与请求追踪的实践与原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgREST 可观测性完全指南:日志、指标与请求追踪的实践与原理

PostgREST 可观测性完全指南:日志、指标与请求追踪的实践与原理

【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

PostgREST 将 PostgreSQL 数据库自动转换为 REST API,而可观测性正是运维这类无状态 API 服务的关键。本文以docs/references/observability.rst为核心骨架,系统讲解 PostgREST 的三类可观测性能力——stdout/stderr日志、Prometheus 指标端点、以及 Server-Timing / Trace Header / EXPLAIN 执行计划等请求追踪手段,并结合仓库源码(Logger.hs、Metrics.hs、Apache.hs)剖析其底层实现。读完本文,你将掌握如何配置log-level/log-query、如何采集连接池与 JWT 缓存指标、如何开启 GHC RTS 指标、以及如何用执行计划定位慢查询。

Logs:两类日志流的区分与配置

PostgREST 的日志分为两个截然不同的输出流,这与 Logger.hs 模块的模块注释一致:"Access logs get sent to stdout and server diagnostic get sent to stderr"(访问日志发往 stdout,服务端诊断日志发往 stderr)。理解这一区分,是正确收集日志的第一步。

访问日志(stdout)

PostgREST 将基本请求信息以 Apache Combined Log 格式写入stdout,包括:经过认证的用户(若可用)、请求方 IP 地址、User-Agent、请求的 URL、HTTP 响应状态码、以及响应体大小(字节,若可用)。

log-level设置为info时,可以看到类似下面的输出:

127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 56 "" "curl/7.64.0" 127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 162 "" "curl/7.64.0"

从源码看,这一格式由 Logger/Apache.hs 中的apacheFormat/apacheLogStr函数生成:依次拼接来源 socket 地址、认证角色(无则输出-)、格式化时间、请求方法与路径(含查询串)、HTTP 版本、状态码、响应字节数(无则-)、Referer 与 User-Agent。该实现直接 vendored 自wai-logger库的 Apache 日志格式,保证与标准 Web 服务器日志格式兼容,可直接接入 Logstash、Filebeat 等采集管道。

访问日志的具体输出行为由 Logger.hs 中的shouldLogResponse决定,它按log-level过滤响应状态码:

log-level访问日志记录范围
crit不记录任何请求
error仅记录5xx状态码(statusCode >= 500
warn记录4xx及以上(statusCode >= 400
info记录所有请求
debug记录所有请求

服务端诊断日志(stderr)

关于服务器自身的诊断信息则写入stderr,主要包括:

  • 所连接 PostgreSQL 数据库的完整版本;
  • schema cache 统计信息(见 schema_cache.rst);
  • listener 收到的数据库通知消息(见 listener.rst)。

启动阶段与运行阶段的典型输出如下:

06/May/2024:08:16:11 -0500: Starting PostgREST 12.1... 06/May/2024:08:16:11 -0500: Successfully connected to PostgreSQL 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit 06/May/2024:08:16:11 -0500: Connection Pool initialized with a maximum size of 10 connections 06/May/2024:08:16:11 -0500: API server listening on port 3000 06/May/2024:08:16:11 -0500: Listening for database notifications on the "pgrst" channel 06/May/2024:08:16:11 -0500: Config reloaded 06/May/2024:08:16:11 -0500: Schema cache queried in 3.8 milliseconds 06/May/2024:08:16:11 -0500: Schema cache loaded 15 Relations, 8 Relationships, 8 RPCs, 0 Domain Representations, 4 Media Type Handlers 06/May/2024:14:11:27 -0500: Received a config reload message on the "pgrst" channel 06/May/2024:14:11:27 -0500: Config reloaded

这些诊断消息全部来自 Logger.hs 的observationMessages函数,它对 PostgREST 内部的各种"观察事件"(Observation)进行文本化:AppStartObs(启动版本)、DBConnectedObs(数据库版本)、PoolInit(连接池大小)、AppServerAddressObs(监听端口)、DBListenStart(监听通道)、SchemaCacheLoadedObs(缓存加载统计)、DBListenerGotConfigMsg/ConfigSucceededObs(配置热加载)等。注意日志行统一带%d/%b/%Y:%T %z格式的时间前缀,例如06/May/2024:08:16:11 -0500

值得强调的是,log-level是可以热加载的(见下文配置小节),Logger.hs 中observationLogger每次处理事件时都会实时读取配置,因此修改配置后无需重启即可调整日志详略。

log-level 与 log-query 配置详解

所有日志行为都受log-level控制(configuration.rst):

  • 类型:String;默认值error可热加载:是;环境变量PGRST_LOG_LEVEL

各取值含义(官方文档原话,逐级递进):

# 仅记录启动与数据库连接恢复消息 log-level = "crit" # 在 crit 基础上,增加服务器错误(5xx 状态码)记录 log-level = "error" # 在 error 基础上,增加请求错误(4xx 状态码)记录 log-level = "warn" # 在 warn 基础上,记录所有请求(所有状态码) log-level = "info" # 在 info 基础上,增加开发调试事件:连接池事件、schema cache 解析耗时等 log-level = "debug"

从源码角度,parseLogLevel在 Config.hs 中完成字符串到LogLevel数据类型的解析,非法值会直接报错Invalid logging level. Check your configuration.debug级别会额外输出连接池借还事件(PoolRequest/PoolRequestFullfilled)、连接建立与终止原因、JWT 缓存查找与驱逐、Warp 服务器内部消息等(见 Logger.hs)。

官方文档特别提示:由于目前日志没有缓冲,crit/error这类最小化日志级别可以提升吞吐量。对高流量生产环境,这值得在配置时权衡。

记录 SQL 查询:log-query

要记录每个请求实际执行的 SQL 查询,将log-query设为true,配合当前的log-level生效(configuration.rst 中默认值为false):

log-level = "warn" log-query = "true"

SQL 查询只在 HTTP 状态码为 400 及以上时才会被记录。例如用户请求一个无权限的资源:

curl "localhost:3000/protected_table"

PostgREST 会输出如下日志——第一行是翻译后的 SQL(含完整的 CTE 包装),第二行是标准的 Apache 访问日志:

17/Feb/2025:17:28:15 -0500: WITH pgrst_source AS ( SELECT "public"."protected_table".* FROM "public"."protected_table" ) SELECT null::bigint AS total_result_set, pg_catalog.count(_postgrest_t) AS page_total, coalesce(json_agg(_postgrest_t), '[]') AS body, nullif(current_setting('response.headers', true), '') AS response_headers, nullif(current_setting('response.status', true), '') AS response_status, '' AS response_inserted FROM ( SELECT * FROM pgrst_source ) _postgrest_t 127.0.0.1 - web_anon [17/Feb/2025:17:28:15 -0500] "GET /protected_table HTTP/1.1" 401 99 "" "curl/8.7.1"

其实现机制在 Logger.hs:当事件为QueryObs MainQuery{..} status时,若shouldLogResponse判定当前状态码需要记录(warn对应>= 400),就把主查询的各个 SQL 片段(事务变量、db-pre-request钩子、主查询、OpenAPI 查询、EXPLAIN 片段等)通过renderSnippet渲染并合并为单行输出。

数据库端日志:从 PostgreSQL 侧观察 SQL

PostgREST 的log-query只记录"出错"的查询。若要观察所有SQL 操作,可以开启 PostgreSQL 自身的语句日志。默认情况下 PostgreSQL 不保留这些日志,需要修改配置。在你的 PostgreSQL 数据目录中找到postgresql.conf(可用show data_directory;命令定位),将以下配置项改为对应值,或直接追加到配置末尾:

# 将日志发送到采集器可访问的位置 log_destination = "stderr" # 将 stderr 输出收集到日志文件 logging_collector = on # 将日志保存到 pg 数据目录下的 pg_log/ log_directory = "pg_log" # (可选)每天新建一个日志文件 log_filename = "postgresql-%Y-%m-%d.log" # 记录所有类型的 SQL 语句 log_statement = "all"

重启数据库后实时跟踪日志文件,即可观察 HTTP 请求如何被翻译为 SQL 命令。

Docker 场景:可以通过自定义init.sh启用日志:

#!/bin/sh echo "log_statement = 'all'" >> /var/lib/postgresql/data/postgresql.conf

然后启动容器并跟踪日志:

docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres docker logs -f <container-id>

Metrics:Prometheus 格式指标端点

PostgREST 的管理服务器(admin server,见 admin_server.rst)上提供了metrics端点,以 Prometheus 文本格式暴露指标:

curl "http://localhost:3001/metrics"

响应示例:

HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 # HELP pgrst_schema_cache_query_time_seconds The query time in seconds of the last schema cache load # TYPE pgrst_schema_cache_query_time_seconds gauge pgrst_schema_cache_query_time_seconds 1.5937927e-2 # HELP pgrst_schema_cache_loads_total The total number of times the schema cache was loaded # TYPE pgrst_schema_cache_loads_total counter pgrst_schema_cache_loads_total 1.0 ...

这些指标的定义集中在 Metrics.hs 的init函数中,底层基于 Haskellprometheus库注册与导出,metricsToText通过exportMetricsAsText输出。指标来源是 PostgREST 内部的观察事件流——observationMetrics函数把PoolAcqTimeoutObsSchemaCacheLoadedObsJwtCacheLookup等事件分别映射为对应计数器的增减(Metrics.hs)。这也解释了为什么这些指标与日志能共享同一套内部事件体系。

Schema Cache 指标

与 schema cache(见 schema_cache.rst)相关的指标:

pgrst_schema_cache_query_time_seconds

类型Gauge

上次 schema cache 加载的查询耗时(秒)。对应源码中SchemaCacheLoadedObs事件触发时setGauge schemaCacheQueryTime resTime

pgrst_schema_cache_loads_total

类型Counter
标签status:SUCCESS | FAIL

schema cache 加载的总次数,按成功/失败打标签。源码中成功时withLabel schemaCacheLoads "SUCCESS" incCounter,失败时withLabel schemaCacheLoads "FAIL" incCounter

Connection Pool 指标

与连接池(见 connection_pool.rst)相关的指标:

pgrst_db_pool_timeouts_total

类型Counter

连接池获取连接超时的总次数。对应PoolAcqTimeoutObs事件触发的incCounter poolTimeouts

pgrst_db_pool_available

类型Gauge

连接池中可用连接数。该值的计算并不简单:hasql-pool对"连接建立成功"和"连接建立失败"都会发出TerminatedConnectionStatus事件,仅凭池事件无法无状态地维护准确的 in-use 连接数,因此 Metrics.hs 用ConnTrack哈希表显式跟踪每条连接(connTrackConnected/connTrackInUse),calcAvailable = connected - inUse计算可用数。

pgrst_db_pool_waiting

类型Gauge

等待获取连接池连接的请求数。PoolRequest事件时incGauge poolWaitingPoolRequestFullfilled事件时decGauge poolWaiting

pgrst_db_pool_max

类型Gauge

连接池最大连接数。初始化时由configDbPoolSize通过setGauge (poolMaxSize metricState)设置。

JWT Cache 指标

与 JWT 缓存相关的指标(见 auth 文档中的 JWT 缓存章节):

pgrst_jwt_cache_requests_total

类型Counter

JWT 缓存查找总次数。源码中JwtCacheLookup事件无论命中与否都会incCounter jwtCacheRequests

pgrst_jwt_cache_hits_total

类型Counter

JWT 缓存命中总次数。命中时同时递增jwtCacheRequestsjwtCacheHits

pgrst_jwt_cache_evictions_total

类型Counter

JWT 缓存驱逐总次数。对应JwtCacheEviction事件。

提示:JWT 缓存命中率直接影响认证阶段耗时(见下文 Server-Timing 的jwt阶段说明),这两组指标可以联动分析。

GHC Runtime 指标:进程健康与内存诊断

PostgREST 还能暴露 GHC 运行时系统指标,使用ghc_*前缀,包含 GHC RTS 统计信息(运行时分配、垃圾回收、内存、CPU/墙钟时间)。这些指标对监控 PostgREST 进程健康、诊断内存压力或 GC 行为非常有价值。

要启用它们,需要以开启 GHC RTS 统计的方式启动 PostgREST:

postgrest +RTS -T -RTS

启用后,admin 的/metrics端点会包含类似采样:

# HELP ghc_gcs_total Total number of GCs # TYPE ghc_gcs_total counter ghc_gcs_total 1 # HELP ghc_allocated_bytes_total Total bytes allocated # TYPE ghc_allocated_bytes_total counter ghc_allocated_bytes_total 12345678

其他可用的 GHC 运行时指标包括:

  • ghc_gcs_total
  • ghc_major_gcs_total
  • ghc_allocated_bytes_total
  • ghc_max_live_bytes
  • ghc_max_mem_in_use_bytes
  • ghc_mutator_cpu_seconds_total
  • ghc_gc_cpu_seconds_total
  • ghc_elapsed_seconds_total

实现上,Metrics.hs 在初始化时通过getRTSStatsEnabled检测 RTS 统计是否开启,开启时才注册Prometheus.Metric.GHCghcMetrics;因此不带+RTS -T -RTS启动时,ghc_*指标不会出现。

Traces:请求链路追踪与性能定位

"追踪"维度覆盖 HTTP 层面的版本头、请求 ID 透传、分阶段耗时头,以及 SQL 执行计划获取,用于在请求级定位问题。

Server 版本响应头

排查问题时,首先需要确认正在运行的 PostgREST 版本。每个响应都带有ServerHTTP 响应头:

HEAD /users HTTP/1.1 Server: postgrest/11.0.1

Trace Header:请求 ID 透传

通过设置server-trace-header(见 configuration.rst),可以启用 HTTP 请求追踪:在请求中携带指定头,服务器会将其原样包含在响应中,便于跨组件串联日志:

server-trace-header = "X-Request-Id"
curl "http://localhost:3000/users" \ -H "X-Request-Id: 123"

响应:

HTTP/1.1 200 OK X-Request-Id: 123

其实现是 App.hs 中的traceHeaderMiddleware中间件:从配置读取configServerTraceHeader,从请求头中查找同名字段,将其(缺失时为空)附加到响应头中。配置解析在 Config.hs(optString "server-trace-header"),并支持配置转储。

Proxy-Status Header

反向代理状态头(Proxy-Status)的相关说明见 proxy_status_header(位于 api.rst 文档)。它用于携带反向代理(如 Nginx、CDN)对请求的处理状态信息。

Server-Timing Header:请求-响应各阶段耗时

开启server-timing-enabled(见 configuration.rst)后,PostgREST 会返回Server-Timing响应头,携带请求-响应周期内各阶段的耗时指标:

curl "http://localhost:3000/users" -i
HTTP/1.1 200 OK Server-Timing: jwt;dur=14.9, parse;dur=71.1, plan;dur=109.0, transaction;dur=353.2, response;dur=4.4
  • 所有dur单位均为毫秒;
  • jwt:执行 JWT 认证的阶段(见 auth.rst)。该耗时可通过 JWT 缓存降低(jwt-cache相关配置);
  • parse:解析 URL 语法阶段(见 url_grammar.rst);
  • plan:利用 schema cache 生成事务主查询的阶段(见 main_query 相关章节);
  • transaction:数据库事务执行阶段(见 transactions.rst);
  • response:计算响应状态与响应头的阶段。

官方文档同时指出,项目正致力于降低parseplan阶段的耗时。实现上,serverTimingHeader位于 Response/Performance.hs,按jwt, parse, plan, transaction, response顺序渲染;App.hs 的withTiming通过timeItT对各阶段计时,configServerTimingEnabled为真时把结果头附加到响应(App.hs)。此外,相关测试位于 ServerTimingSpec.hs,可作为行为参考。

Content-Length Header:响应体大小验证

可以通过Content-Length响应头验证响应体大小(字节):

curl -i 'localhost:3000/users'
HTTP/1.1 200 OK Content-Length: 104

注意:出于优化目的,HEAD请求不会返回该头(见 head_req 相关说明),这与 RFC 9110 的规定一致。响应体大小同样出现在 PostgREST 的访问日志中(即 Apache 日志格式里的字节数字段)。

Execution plan:获取 EXPLAIN 执行计划

为请求添加Accept: application/vnd.pgrst.plan头即可获取其 EXPLAIN 执行计划。此功能由db-plan-enabled开启(默认false,见 configuration.rst):

curl "http://localhost:3000/users?select=name&order=id" \ -H "Accept: application/vnd.pgrst.plan"

输出(text 格式):

Aggregate (cost=73.65..73.68 rows=1 width=112) -> Index Scan using users_pkey on users (cost=0.15..60.90 rows=850 width=36)

计划默认以text格式生成,可通过+json后缀改为 JSON:

curl "http://localhost:3000/users?select=name&order=id" \ -H "Accept: application/vnd.pgrst.plan+json"
[ { "Plan": { "Node Type": "Aggregate", "Strategy": "Plain", "Partial Mode": "Simple", "Parallel Aware": false, "Async Capable": false, "Startup Cost": 73.65, "Total Cost": 73.68, "Plan Rows": 1, "Plan Width": 112, "Plans": [ { "Node Type": "Index Scan", "Parent Relationship": "Outer", "Parallel Aware": false, "Async Capable": false, "Scan Direction": "Forward", "Index Name": "users_pkey", "Relation Name": "users", "Alias": "users", "Startup Cost": 0.15, "Total Cost": 60.90, "Plan Rows": 850, "Plan Width": 36 } ] } } ]

for参数:默认计划假设资源以 JSON 表示(application/json),但可以通过for参数获取 PostgREST 支持的不同表示(见 res_format)的计划。例如获取text/xml表示的计划:

Accept: application/vnd.pgrst.plan; for="text/xml"

options参数:其他可用参数为analyzeverbosesettingsbufferswal,与 EXPLAIN 命令选项一一对应。例如同时使用analyzewal

Accept: application/vnd.pgrst.plan; options=analyze|wal

工作流参考:如需从 verbose 计划中提取Query Identifier,并在pg_stat_statements中检查同一条查询,可参考 debugging-performance-with-pg-stat-statements.rst。

重要注意事项:与 EXPLAIN 命令类似,使用analyze选项时变更会被提交。要避免提交,可以配合db-tx-end配置与Prefer: tx=rollback头使用(见 transactions.rst)。

保护执行计划功能

官方强烈建议仅在测试环境启用db-plan-enabled,因为它会泄露数据库内部细节。若确需在生产环境使用,可以通过db-pre-request(见 configuration.rst)限制可使用该功能的请求。例如,仅允许特定 IP 获取执行计划:

-- 假设反向代理(Nginx、Cloudflare 等)传递 "X-Forwarded-For" 头 create or replace function filter_plan_requests() returns void as $$ declare headers json := current_setting('request.headers', true)::json; client_ip text := coalesce(headers->>'x-forwarded-for', ''); accept text := coalesce(headers->>'accept', ''); begin if accept like 'application/vnd.pgrst.plan%' and client_ip != '144.96.121.73' then raise insufficient_privilege using message = 'Not allowed to use application/vnd.pgrst.plan'; end if; end; $$ language plpgsql; -- 在 postgrest.conf 中配置 -- db-pre-request = filter_plan_requests

该函数读取request.headers(GUC 头机制,见 guc_header 相关章节)中的acceptx-forwarded-for,对非白名单 IP 的 plan 请求抛出insufficient_privilege,从而在不关闭功能的前提下收紧访问。

结语:如何组合使用三层可观测性

PostgREST 的可观测性设计遵循经典的 logs / metrics / traces 三支柱模型,且全部围绕同一套内部观察事件体系(PostgREST.Observation)实现,源码中 Logger.hs 与 Metrics.hs 分别消费这些事件产出日志与指标,架构清晰、易于扩展。实际运维时可参考以下组合策略:

  • 日常监控:开启 admin server 的/metrics端点,配合 Prometheus + Grafana 采集pgrst_db_pool_*pgrst_schema_cache_*pgrst_jwt_cache_*,并在启动命令中加上+RTS -T -RTS以获得ghc_*运行时指标;
  • 故障定位:设置server-trace-header透传X-Request-Id,开启server-timing-enabled观察各阶段耗时分布(jwt/parse/plan/transaction/response),再结合log-level = "warn"+log-query = "true"捕获 4xx/5xx 对应的 SQL;
  • 性能调优:在测试环境开启db-plan-enabled,用Accept: application/vnd.pgrst.plan+json获取 JSON 执行计划,结合pg_stat_statements做深入分析,生产环境务必用db-pre-request做访问控制。

相关配置项速查(详见 configuration.rst):

配置项默认值作用
log-levelerror日志详细程度(crit/error/warn/info/debug),可热加载
log-queryfalse是否在 4xx/5xx 时记录对应 SQL
server-trace-header透传的请求追踪头名
server-timing-enabledfalse是否输出 Server-Timing 分阶段耗时
db-plan-enabledfalse是否允许通过 Accept 头获取 EXPLAIN 计划
db-pre-request请求前置函数,可限制 plan 等功能的使用

相关源码与测试入口:日志格式 Logger/Apache.hs、日志实现 Logger.hs、指标实现 Metrics.hs、Server-Timing 渲染 Response/Performance.hs、Trace Header 中间件 App.hs、配置解析 Config.hs、行为测试 ServerTimingSpec.hs。

【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

从线上事故到生产实践:Redis核心原理与高可用架构全解析

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

作者头像 李华
网站建设 2026/9/11 4:39:10

Pico+MicroPython实现稳定MQTT订阅的工程实践

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

作者头像 李华
网站建设 2026/9/11 4:38:55

模拟退火算法:从物理现象到全局优化解决方案

1. 从沸腾金属到最优解&#xff1a;一个物理学家的意外发现1983年&#xff0c;美国贝尔实验室的两位科学家Kirkpatrick、Gelatt和Vecchi在《科学》杂志上发表了一篇改变优化算法历史的论文。他们当时正在研究金属退火过程中的原子重排现象——将金属加热至高温后缓慢冷却&#…

作者头像 李华
网站建设 2026/9/11 4:38:25

Agent记忆系统三层架构与跨会话持久化实战

1. 为什么“让 Agent 记住你”不是功能&#xff0c;而是系统级分水岭“走进AI Agent第三篇&#xff1a;让 Agent 记住你”——这个标题乍看像一句温情的文案&#xff0c;实则藏着当前Agent工程落地中最硬的骨头。我带团队做过7个生产级Agent项目&#xff0c;前4个都卡在第二周&…

作者头像 李华
网站建设 2026/9/11 4:35:17

Dify实战:从本地部署到生产级RAG知识库与工作流编排

我最早接触 Dify&#xff0c;是去年帮一家客户做政务类的 RAG 知识库项目。当时团队里几个人对“AI 应用定制化”的理解还停留在调 API、套 Prompt 的阶段&#xff0c;结果一上手才发现&#xff0c;真正的拦路虎根本不在模型&#xff0c;而是怎么把知识库、工作流、外部工具揉进…

作者头像 李华