上周在一台全新的CentOS 7.9服务器上部署GeoServer,环境是JDK8 + Tomcat9 + GeoServer 2.25。发布完第一批土地利用数据后,我打开WMS预览,地图里的中文标注全部变成了一排排方框,类似Win98时代没装中文字库打开网页的效果。当时同事第一反应是"编码有问题",改了一下午字符集,结果毫无变化。这个坑我在过去几年里踩过七八回,每次都能绕进同一个误判里。这次干脆把GeoServer在CentOS下中文变方框的完整定位思路和终极解法整理出来,一次说透。
先说结论:在CentOS上部署GeoServer后,地图文字显示为方框,绝大多数不是编码问题,而是Linux服务器缺少中文字体。GeoServer负责渲染地图标注时,会把文本交给Java的图形渲染层去画,Java在Linux下需要从系统字体库里找字形,找不到中文字形,就只能输出一个"豆腐块"方框。整个过程跟Shapefile属性乱码、数据库编码、Tomcat编码完全是两码事。后面的内容我会从症状区分、根因定位、安装字体、SLD联动到防复发,按照实际排查顺序一步步来。
1. 先分清你到底踩了哪种"乱码":方框、问号和锟斤拷是三个世界
1.1 "显示方框"与"属性乱码"的本质差异
中文显示异常有几种完全不同的表现,网上搜出来的解决方案混在一起,经常让人做无用功。最典型的有三种:
- 方框/豆腐块(ToFu):每个汉字显示成一个中空的方框,部分版本还会带个十六进制数字。这是"字形缺失",意思是渲染引擎已经拿到正确的字符编码,但在字体库里找不到对应的汉字轮廓。
- 问号(?????):通常是数据读取时字符集转码失败。比如Shapefile的DBF文件是GBK编码,GeoServer默认按UTF-8去解析,读出来就变成问号。
- 锟斤拷/烫烫烫:这是UTF-8字节流被GBK解码再转回UTF-8后遗留的乱码,多见于历史数据迁移、老系统导出的文本文件。
我遇到的地图标注方框就属于第一种。编码层面一切正常,数据库里的中文没有问题,GeoServer内部字符串也没有问题,只是最后一步"把汉字画出来"的时候,字体库找不到中文字形。理解这一点非常重要,因为后续所有操作都围绕"让JVM能找到中文字体"展开,而不是去折腾URIEncoding或者characterEncoding。
1.2 一张表快速自诊:四种典型症状对应的问题范围
我在排查现场一般会让同事先看症状属于哪一类,再决定往哪个方向查:
| 症状表现 | 常见出现位置 | 根因方向 |
|---|---|---|
| 地图标注显示□□□方框 | WMS预览、图例、打印PDF | 系统缺少中文字体、SLD字体族名不匹配 |
| 属性表字段返回????问号 | WFS查询、要素属性、图层预览表 | Shapefile的DBF字符集、数据库连接串缺少UTF-8 |
| 图层名/工作区名乱码 | 管理后台数据区 | Tomcat的URIEncoding、GeoServer内部编码配置 |
| 后台页面文字乱码 | GeoServer管理页面本身 | Tomcat或浏览器字符集强制错误 |
这张表不绝对,但能帮你快速缩小排查范围。我见过很多人在第一行的问题里折腾第三行的解决方案,比如给Tomcat加编码过滤器去解决方框问题,搞了半天完全没用。记住:方框=字形缺失,问号=编码错误,这是两条路。
1.3 我在现场最常见的误判
有一次客户报障说"地图上所有中文变方框",我先远程看了下GeoServer日志,没报任何错;再看了数据库连接串,已经是UTF-8;又检查了Tomcat的server.xml,URIEncoding也是对的。所有编码环节都没有问题,但方框就在那里。后来我才意识到,问题根本不在数据链路,而是这台服务器装的是CentOS最小化安装,系统里压根没有任何中文字体。
这类误判在论坛里太常见了。很多人一看到中文变方块,条件反射就搜索"乱码解决方案",然后往编码方向投入大量时间。说实话,第一次遇到时我自己也绕了弯路。所以这篇博文的第一个价值点就是:别用查编码的思路查方框。后面所有方案都建立在"字体缺失"这个根因上。
2. 根因定位:CentOS最小化服务器的"隐形缺字体"排查链路
2.1 为什么CentOS装了GeoServer后一定会显示方框
CentOS 7以后的服务器安装,很多人习惯选择Minimal模式,连桌面环境和GUI都不装,这样系统体积最小。但字体库这块也被裁掉了:系统只保留最基础的英文字体,中文字体一个都没有。更麻烦的是GeoServer自身只携带Java平台的逻辑字体(Serif、SansSerif、Monospaced),不带任何系统的字体文件。所以一旦SLD里写着要渲染中文,Java2D渲染器会在系统字体目录和fontconfig配置里去匹配字形,结果是根本没有可用的中文字体。
有人会问"那我本机Windows上跑GeoServer怎么好好的?"因为Windows自带宋体、黑体、微软雅黑等大量中文字体,Java装上去就能直接用。Linux服务器不一样,尤其是云服务器/内网机房的CentOS,默认就是不给你带中文字体的。
2.2 三步确认系统是否缺中文字体
在动手装字体之前,先花一分钟确认根因。按顺序执行这三步:
第一步:系统字体列表里有没有中文字体
fc-list :lang=zh如果输出为空,或者提示No fonts found,就说明系统确实没有注册任何中文字体。这一步基本就能下判断了。如果输出里有中文字体信息,比如WenQuanYi Micro Hei,那系统层面不缺字体,问题在别处。
第二步:查看字体目录里的实际内容
ls -l /usr/share/fonts/CentOS默认的字体目录是/usr/share/fonts/,里面通常有dejavu、urw-base35这类英文字体目录。如果看不到任何中文相关的目录,说明没有中文字体文件。有些管理员会往/usr/share/fonts/里塞.ttf文件但没有执行fc-cache,导致字体文件在但未被系统注册,这种情况也会显示方框。
第三步:去GeoServer后台看Fonts列表
登录GeoServer管理界面,找到"服务器"菜单下的"字体"页面。这里会列出当前JVM可识别的所有字体族名,通常只有Dialog、DialogInput、Monospaced、SansSerif、Serif五种逻辑字体。如果你看不到任何中文字体族名,就彻底实锤了:GeoSerever连一个中文字体都没读到。
这三步走完,基本能确定是不是字体缺失。我建议至少执行第一步和第三步,因为系统有字体不代表Java一定能读到,后台Fonts列表才是最权威的结论。
2.3 排查过程中容易踩的假线索
排查阶段有几个"假线索"特别浪费时间和精力,我说一下:
- GeoServer日志无报错。字体缺失的时候GeoServer通常不会在日志里打任何Error,只会在图片输出里画方框,所以"日志没报错"不能排除字体问题。
- 把锅甩给Tomcat编码。Tomcat的URIEncoding、useBodyEncodingForURI只影响HTTP请求里的参数解析,和最后渲染字形的方框没有直接关系。
- 以为是数据源头坏了。如果你发布的是PostGIS数据,可能会怀疑是不是数据库里存的中文有问题。用pgAdmin或者QGIS直接连库看,中文显示正常,那数据没问题,问题在渲染端。
这类假线索之所以迷惑人,是因为"方框"这个症状太容易被归类为"乱码",而"乱码"又太容易让人联想到编码配置。我个人的排查习惯是:遇到地图文字方框,先看Fonts列表,而不是先翻server.xml。
3. 终极解决方案:把中文字体"装进"JVM能读到的位置
3.1 方案A:yum安装开源中文字体,别再手动传字体文件了
这是最省事、也最推荐的方案,直接用包管理器安装开源的文泉驿字体。
在CentOS 7上:
# 如果还没启用EPEL,先装epel-release yum install -y epel-release # 安装文泉驿微米黑和正黑 yum install -y wqy-microhei-fonts wqy-zenhei-fonts如果仓库里没有这两个包,或者你更习惯明体系字体,可以装:
yum install -y cjkuni-uming-fonts cjkuni-ukai-fonts在CentOS 8/9 Stream或者Rocky Linux 8/9上,把yum换成dnf即可:
dnf install -y wqy-microhei-fonts wqy-zenhei-fonts如果公司要求使用Noto字体(Google出品的开源字体族,显示效果更现代),也可以装Noto CJK:
# CentOS 7可能需要先启用epel yum install -y google-noto-sans-cjk-fonts装完之后,文泉驿微米黑对应的字体族名是WenQuanYi Micro Hei,文泉驿正黑对应WenQuanYi Zen Hei,Noto Sans CJK SC对应Noto Sans CJK SC,这些名字后面在SLD里要直接用到,提前记下来。
3.2 方案B:离线服务器的字体上传与注册
内网环境或没有公网源的时候,从外部拷贝字体文件是常见做法。这也是很多政企项目的真实场景,毕竟GIS服务器多数在内网机房。
操作流程:
# 1. 创建字体目录 mkdir -p /usr/share/fonts/chinese # 2. 用sftp/scp把字体文件传上去,比如simhei.ttf、NotoSansSC-Regular.otf # 例如: scp simhei.ttf root@服务器IP:/usr/share/fonts/chinese/ # 3. 给字体文件加上可读权限 chmod -R 644 /usr/share/fonts/chinese/ # 4. 更新字体缓存 fc-cache -fv这里有个容易被忽略的坑:Java老版本对.ttc(TrueType Collection)字体的支持不稳定。如果你从Windows系统目录里拷simsun.ttc、msyh.ttc这类集合文件,很可能放在fc-list里能看到字体,但渲染中文时Java还是画方框。能传.ttf或.otf就优先传这两种格式,比如单文件的simhei.ttf(黑体)就比simsun.ttc(宋体)靠谱。另外,从Windows拷贝的商用字体会涉及授权问题,内部使用还好,对外提供服务建议选文泉驿或者Noto这类开源字体。
3.3 刷新fontconfig缓存,让系统先"看见"字体
不管是yum装的还是手动上传的,字体文件并不会自动被系统识别。Linux的字体查找机制依赖fontconfig的缓存索引,所以必须刷新一次:
fc-cache -fv正常输出里能看到类似/usr/share/fonts/chinese: caching, new cache contents: 1 fonts的字样。然后再次验证:
fc-list :lang=zh如果能看到中文字体列表,说明系统层面已经认到了。
3.4 设置JVM无头渲染参数,别让Java走弯路
字体装好了,系统也认到了,但JVM不一定马上能用。这里有一个关键参数:java.awt.headless。GeoServer在服务器端渲染地图,本质上跑在无图形界面的环境里,必须明确告诉JVM进入无头模式,否则字体渲染逻辑走的是带头模式的路径,容易出现莫名其妙的字体加载失败。
推荐把JVM参数加到Tomcat的启动环境变量中。以Tomcat为例,修改bin/setenv.sh(没有就创建这个文件):
export CATALINA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xms512m -Xmx2048m"如果你用的是CentOS自带的tomcat服务,可以修改/etc/tomcat/tomcat.conf里的JAVA_OPTS。
-Dfile.encoding=UTF-8也很重要,虽然它不是方框问题的直接根因,但能避免Java内部字节流转换时出现字符集歧义,属于顺手的防护配置。
还有一个更底层的兜底方案:直接把字体文件复制到JVM自己的字体目录里,绕开fontconfig的桥接。以JDK8为例:
# 找到JAVA_HOME echo $JAVA_HOME # 创建fallback目录 mkdir -p $JAVA_HOME/jre/lib/fonts/fallback # 把字体复制过去(注意优先ttf/otf) cp /usr/share/fonts/chinese/wqy-microhei.ttc $JAVA_HOME/jre/lib/fonts/fallback/这种方式比较粗暴,但能解决一些极端情况,比如Java版本较老、fontconfig桥接失效。做这一步有个前提:你装的是Oracle JDK或者OpenJDK,路径里确实有jre/lib/fonts目录。如果用的是打了精简补丁的JDK,目录结构可能不同,先检查一下再动手。
3.5 重启后的第一件事:回后台看Fonts列表
所有字体配置完成后,重启Tomcat:
# 如果你用systemd管理tomcat systemctl restart tomcat # 如果是手动安装的tomcat /opt/tomcat/bin/shutdown.sh /opt/tomcat/bin/startup.sh重启完,重新打开GeoServer管理后台的"字体"页面,你会看到字体列表里多出不少家族,常见的包括WenQuanYi Micro Hei、WenQuanYi Zen Hei,以及系统逻辑字体。到这一步,系统的字体供给链路已经打通,地图标注不再画方框了。
但请注意,到这里只算完成了60%。剩下40%的问题是SLD样式里是否引用了正确字体族名、打印模块是否识别字体、GeoWebCache缓存是否还在吐旧瓦片,这些恰恰是最容易让人"装完字体还显示方框"的原因。
4. SLD样式、WMS输出与打印模块:字体联动三连击
4.1 SLD字体族名:写"宋体"没用,要用系统注册的族名
这是我认为整个流程里最容易再次翻车的地方。很多人在SLD里写:
<CssParameter name="font-family">宋体</CssParameter>然后在Linux服务器上完全没有效果。原因很简单:系统里根本没有注册名为"宋体"的字体族。即使你装了中文字体,它注册的族名也可能是WenQuanYi Micro Hei、AR PL UMing CN这类拼音/英文名称。SLD里font-family必须匹配系统实际注册的字体族名,否则GeoServer会回退到默认字体,方框依然存在。
所以在写SLD之前,回到后台Fonts页面,用页面上显示的实际字体族名去填。常见对应关系:
| 安装的字体包 | 实际字体族名 |
|---|---|
| wqy-microhei-fonts | WenQuanYi Micro Hei |
| wqy-zenhei-fonts | WenQuanYi Zen Hei |
| cjkuni-uming-fonts | AR PL UMing CN |
| google-noto-sans-cjk-fonts | Noto Sans CJK SC |
我一般建议团队统一用Noto Sans CJK SC或者WenQuanYi Micro Hei,一个作为标准字体族名写进所有SLD模板,方便后期统一调整。
4.2 一份可直接复用的中文标注SLD模板
下面是一份完整的SLD,可用于点图层的名称标注,font-family已经写成WenQuanYi Micro Hei:
<?xml version="1.0" encoding="UTF-8"?> <StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"> <UserLayer> <Name>label_zh</Name> <UserStyle> <Name>label_zh</Name> <FeatureTypeStyle> <Rule> <TextSymbolizer> <Label> <ogc:PropertyName>name</ogc:PropertyName> </Label> <Font> <CssParameter name="font-family">WenQuanYi Micro Hei</CssParameter> <CssParameter name="font-size">14</CssParameter> <CssParameter name="font-style">normal</CssParameter> <CssParameter name="font-weight">normal</CssParameter> </Font> <Fill> <CssParameter name="fill">#000000</CssParameter> </Fill> </TextSymbolizer> </Rule> </FeatureTypeStyle> </UserStyle> </UserLayer> </StyledLayerDescriptor>在这个模板里,<ogc:PropertyName>name</ogc:PropertyName>读取数据源里的name字段作为标注内容。如果你的字段名不同,替换成实际字段名即可。保存为label_zh.sld,在GeoServer发布图层样式的页面里上传使用。
这里我补充一个常见经验:如果使用WMS请求时通过STYLES=样式名引用SLD,请确保URL里的样式名和图层参数都是UTF-8编码的。GeoServer的WMS服务在解析中文图层名/样式名时,跟Tomcat的URIEncoding有一定关系。Tomcat 8以上默认URI编码是UTF-8,问题不大。但如果你的Tomcat手动改过server.xml里的URIEncoding="GBK",那就要小心,WMS请求里带中文会挂掉。
4.3 WMS出图和GeoWebCache的缓存清理
装好字体、改好SLD之后,很多人的第一反应是刷新浏览器预览页面,结果发现还是方框。别急,大概率是GeoWebCache把旧的瓦片缓存下来了。
GeoServer内置了瓦片缓存,当你请求http://ip:8080/geoserver/gwc/service/wms这类带gwc路径的服务时,第一次渲染的图片会被缓存起来。字体修复前生成的瓦片如果还在缓存中,WMS就一直是旧图。
解决方式:
- 临时验证:请求时加个随机参数,比如
&_=1234567890,强制绕过缓存。 - 彻底清理:进入GeoServer后台的瓦片缓存管理页面,找到对应图层,清空GWC缓存。
- 如果用了独立的GeoWebCache,删除
gwc缓存目录下的相关图层文件夹。
我个人的建议是:字体修复和SLD改造完成之后,先强制刷新一次WMS预览,确认确实好了,再去清理全部瓦片缓存。不要一上来就清缓存,否则会把"样式调整前"和"样式调整后"的瓦片混在一起,搞不清楚到底是哪个环节生效了。
4.4 打印模块的字体目录也要单独照顾
如果你启用了GeoServer的打印扩展(Print模块,用来输出PDF),那么这里还有另一个独立的字体环节。打印模块基于MapFish Print引擎,它在自己的进程里渲染文字,不直接复用GeoServer主进程的字体列表。有时候地图预览已经正常,但打印出来的PDF依然中文变方框,问题就出在打印模块没有读到中文字体。
打印模块的字体配置通常在它的配置文件(print/config.yaml或类似文件)中。解决思路有两个:
- 确保打印模块的JVM能读到系统字体。如果打印模块和GeoServer跑在同一个Tomcat里,通常重启Tomcat后就能生效。
- 在打印配置里显式指定字体目录和字体族名。比如你安装的是
WenQuanYi Micro Hei,在配置里把字体族名设置成WenQuanYi Micro Hei,而不是默认的Helvetica。
要验证打印模块是否已经可用中文字体,最直接的办法是打印一份带中文的PDF看看。如果还是方框,先检查打印模块的配置文件里是否额外指定了字体目录,把/usr/share/fonts/chinese加进去,再重启服务。
5. 防复发检查清单与扩展:从命令行到浏览器的完整验证
5.1 一分钟自检命令:新机器开箱检查
说实话,这条路走通一次实在不容易。我在内部团队里沉淀了一套"新服务器开箱检查"的脚本思路,每次部署完GeoServer,按这个顺序跑一遍,基本能在发布数据之前就发现字体隐患。
# 1. 看系统有没有中文字体 fc-list :lang=zh # 2. 看关键字体目录 ls -l /usr/share/fonts/ | grep -i -E "wqy|noto|cjk|chinese" # 3. 确认JAVA_HOME和Java版本 echo $JAVA_HOME java -version # 4. 确认Tomcat的JVM参数 cat /opt/tomcat/bin/setenv.sh 2>/dev/null || cat /etc/tomcat/tomcat.conf | grep -i java_opts跑完这四条命令,你基本能判断这台机器是否具备中文渲染能力。如果fc-list :lang=zh输出为空,直接在部署阶段就装字体,别等上线后再来回折腾。
5.2 容易被误判成"乱码"的其他场景:DBF编码、连接串、Tomcat编码
虽然方框问题主要是字体缺失,但有一类"假方框"值得提一嘴。当你发布一个Shapefile图层时,如果属性表里的中文在"图层预览"中显示成问号而不是方框,那是DBF字符集的问题,跟字体无关。Shpfile的编码由.dbf文件自带的信息决定,而GeoServer在Store配置里有一个"DBF字符集"选项。遇到中文乱码,把它从UTF-8改成GBK或者GB2312试试,这是发布Shapefile的老问题了。
PostGIS数据源同理,连接串里加上:
jdbc:postgresql://127.0.0.1:5432/gisdb?characterEncoding=UTF-8这个参数指定了JDBC连接使用UTF-8进行字符集转换,避免读取中文属性时变成问号。
还有一类场景:GeoServer后台界面本身出现中文乱码(比如登录页、菜单)。那通常不是字体问题,而是浏览器强制指定了错误字符集,或者Tomcat的URIEncoding配置成非UTF-8。你可以在Tomcat的server.xml里确认:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" useBodyEncodingForURI="true" />这些属于数据链路编码的问题,虽然不直接产生方框,但经常和字体问题一起被混着搜,所以放在防复发清单里一并提醒。
5.3 团队级固化:把字体安装写进初始化脚本
最后说点实战层面的经验。这种"显示方框"的问题,最怕的就是每次部署新服务器都要重新踩一遍。我建议把字体安装整理成一段原子脚本,固化在服务器的初始化配置里。以Ansible为例,可以写一个简单的playbook:
- name: Install Chinese fonts for GeoServer hosts: geoserver tasks: - name: Install font packages yum: name: - wqy-microhei-fonts - wqy-zenhei-fonts - google-noto-sans-cjk-fonts state: present when: ansible_os_family == "RedHat" - name: Refresh font cache command: fc-cache -fv即使不用Ansible,也可以直接维护一小段Shell脚本,装完系统后先跑一遍,再装GeoServer。这样后续新环境上线,基本不会再出现中文方框的报障。
我个人的实际体会是,这类问题90%出在三个环节:系统字体缺失、SLD字体族名写错、瓦片缓存未清。只要把这三关一一验证到位,剩下的10%基本就是打印模块和DBF编码这些边角问题。希望这篇整理能让你少走几趟弯路,一次把GeoServer的中文显示问题彻底解决。