1+ import { getText } from "@vuepress/helper" ;
12import { getDirname , path } from "vuepress/utils" ;
23import { hopeTheme } from "vuepress-theme-hope" ;
34
@@ -21,6 +22,176 @@ const docsearchOptions =
2122 } ,
2223 }
2324 : null ;
25+ const MIN_META_DESCRIPTION_LENGTH = 150 ;
26+ const MAX_META_DESCRIPTION_LENGTH = 160 ;
27+
28+ const segmentDisplayNames = {
29+ ai : "AI" ,
30+ "ai-coding" : "AI 编程" ,
31+ algorithms : "算法" ,
32+ basis : "基础知识" ,
33+ books : "技术书籍" ,
34+ collection : "Java 集合" ,
35+ concurrent : "Java 并发" ,
36+ "cs-basics" : "计算机基础" ,
37+ "data-structure" : "数据结构" ,
38+ database : "数据库" ,
39+ "distributed-process-coordination" : "分布式协调" ,
40+ "distributed-system" : "分布式系统" ,
41+ docker : "Docker" ,
42+ elasticsearch : "Elasticsearch" ,
43+ framework : "开发框架" ,
44+ git : "Git" ,
45+ gradle : "Gradle" ,
46+ "high-availability" : "高可用" ,
47+ "high-performance" : "高性能" ,
48+ "interview-preparation" : "面试准备" ,
49+ io : "Java IO" ,
50+ java : "Java" ,
51+ javaguide : "JavaGuide" ,
52+ jvm : "JVM" ,
53+ "message-queue" : "消息队列" ,
54+ mysql : "MySQL" ,
55+ network : "计算机网络" ,
56+ "new-features" : "Java 新特性" ,
57+ "open-source-project" : "开源项目" ,
58+ "operating-system" : "操作系统" ,
59+ protocol : "分布式协议与算法" ,
60+ rag : "RAG" ,
61+ redis : "Redis" ,
62+ rpc : "RPC" ,
63+ security : "安全" ,
64+ sql : "SQL" ,
65+ "system-design" : "系统设计" ,
66+ tools : "开发工具" ,
67+ zookeeper : "ZooKeeper" ,
68+ } ;
69+
70+ const normalizeDescriptionText = ( value ) =>
71+ String ( value ?? "" )
72+ . replace ( / < [ ^ > ] + > / g, " " )
73+ . replace ( / & n b s p ; / g, " " )
74+ . replace ( / & a m p ; / g, "&" )
75+ . replace ( / & l t ; / g, "<" )
76+ . replace ( / & g t ; / g, ">" )
77+ . replace ( / & q u o t ; / g, '"' )
78+ . replace ( / & # 3 9 ; / g, "'" )
79+ . replace ( / \s + / g, " " )
80+ . trim ( ) ;
81+
82+ const toArray = ( value ) => {
83+ if ( Array . isArray ( value ) ) return value ;
84+ return value ? [ value ] : [ ] ;
85+ } ;
86+
87+ const formatPathSegment = ( segment ) =>
88+ segmentDisplayNames [ segment ] ??
89+ decodeURIComponent ( segment )
90+ . replace ( / - / g, " " )
91+ . replace ( / \b \w / g, ( char ) => char . toUpperCase ( ) ) ;
92+
93+ const getPathTopic = ( page ) =>
94+ page . path . split ( "/" ) . filter ( Boolean ) . map ( formatPathSegment ) . join ( " / " ) ;
95+
96+ const getHeaderTitles = ( page ) =>
97+ toArray ( page . headers )
98+ . map ( ( { title } ) => normalizeDescriptionText ( title ) )
99+ . filter ( Boolean )
100+ . slice ( 0 , 4 ) ;
101+
102+ const getPageText = ( page , app ) =>
103+ normalizeDescriptionText (
104+ getText (
105+ page . data . excerpt ?? page . contentRendered ?? page . content ?? "" ,
106+ app . siteData . base ,
107+ {
108+ length : 220 ,
109+ singleLine : true ,
110+ } ,
111+ ) ,
112+ ) ;
113+
114+ const trimDescription = ( description ) => {
115+ if ( description . length <= MAX_META_DESCRIPTION_LENGTH ) return description ;
116+
117+ const trimmed = description . slice ( 0 , MAX_META_DESCRIPTION_LENGTH ) ;
118+ const lastStop = Math . max (
119+ trimmed . lastIndexOf ( "。" ) ,
120+ trimmed . lastIndexOf ( "!" ) ,
121+ trimmed . lastIndexOf ( "?" ) ,
122+ trimmed . lastIndexOf ( ";" ) ,
123+ trimmed . lastIndexOf ( ";" ) ,
124+ ) ;
125+
126+ if ( lastStop >= MIN_META_DESCRIPTION_LENGTH - 5 )
127+ return trimmed . slice ( 0 , lastStop + 1 ) ;
128+
129+ const lastSoftStop = Math . max (
130+ trimmed . lastIndexOf ( "," ) ,
131+ trimmed . lastIndexOf ( "、" ) ,
132+ trimmed . lastIndexOf ( "," ) ,
133+ ) ;
134+
135+ if ( lastSoftStop >= MIN_META_DESCRIPTION_LENGTH - 5 ) {
136+ const base = trimmed . slice ( 0 , lastSoftStop ) . replace ( / [ , 、 , ; \s ] + $ / , "" ) ;
137+ const result = `${ base } 等核心内容。` ;
138+
139+ return result . length <= MAX_META_DESCRIPTION_LENGTH
140+ ? result
141+ : `${ result . slice ( 0 , MAX_META_DESCRIPTION_LENGTH - 1 ) } 。` ;
142+ }
143+
144+ return `${ description . slice ( 0 , MAX_META_DESCRIPTION_LENGTH - 1 ) } 。` ;
145+ } ;
146+
147+ const buildSeoDescription = ( page , app ) => {
148+ const existingDescription = normalizeDescriptionText (
149+ page . frontmatter . description ,
150+ ) ;
151+
152+ if ( existingDescription . length >= MIN_META_DESCRIPTION_LENGTH )
153+ return trimDescription ( existingDescription ) ;
154+
155+ if ( page . path === "/" )
156+ return trimDescription (
157+ "JavaGuide 是一份面向 Java 后端开发者和面试准备人群的学习指南,系统覆盖 Java 基础、集合、并发、JVM、MySQL、Redis、分布式、高并发、高可用、系统设计、消息队列、计算机基础和 AI 应用开发等核心知识,适合校招社招复习、查缺补漏和规划学习路线。" ,
158+ ) ;
159+
160+ if ( page . path === "/home.html" )
161+ return trimDescription (
162+ "JavaGuide 首页聚合 Java 后端学习路线、核心知识体系和高频面试题入口,覆盖 Java 基础、并发、JVM、数据库、Redis、分布式、系统设计、高性能、高可用、计算机基础和 AI 应用开发,帮助读者快速定位重点内容。" ,
163+ ) ;
164+
165+ if ( page . path === "/404.html" )
166+ return trimDescription (
167+ "JavaGuide 页面未找到提示页,帮助读者返回 Java 面试指南、后端通用面试知识、计算机基础、数据库、Redis、分布式、系统设计和 AI 应用开发等核心内容入口,继续定位学习资料、面试题总结和实践文章。" ,
168+ ) ;
169+
170+ const title = normalizeDescriptionText ( page . title ) ;
171+ const category = toArray ( page . frontmatter . category )
172+ . map ( normalizeDescriptionText )
173+ . filter ( Boolean ) ;
174+ const tags = toArray ( page . frontmatter . tag ?? page . frontmatter . tags )
175+ . map ( normalizeDescriptionText )
176+ . filter ( Boolean )
177+ . slice ( 0 , 4 ) ;
178+ const headers = getHeaderTitles ( page ) ;
179+ const focusItems = [ ...headers , ...tags ] . filter ( Boolean ) . slice ( 0 , 5 ) ;
180+ const topic = getPathTopic ( page ) || title || category [ 0 ] || "JavaGuide" ;
181+ const pageText = getPageText ( page , app ) ;
182+ const parts = [
183+ existingDescription || ( title ? `${ title } :` : "" ) ,
184+ focusItems . length ? `重点围绕 ${ focusItems . join ( "、" ) } 等内容展开。` : "" ,
185+ `结合 JavaGuide 知识体系梳理 ${ topic } 的核心概念、实践方法、常见问题和高频面试考点,覆盖原理分析、使用场景、方案对比与经验总结,适合后端开发者系统学习、面试复习、快速定位重点内容和查缺补漏。` ,
186+ pageText && ! existingDescription . includes ( pageText . slice ( 0 , 24 ) )
187+ ? pageText
188+ : "" ,
189+ ] ;
190+
191+ return trimDescription (
192+ normalizeDescriptionText ( parts . filter ( Boolean ) . join ( "" ) ) ,
193+ ) ;
194+ } ;
24195
25196export default hopeTheme ( {
26197 hostname : "https://javaguide.cn/" ,
@@ -80,7 +251,17 @@ export default hopeTheme({
80251 seo : {
81252 canonical : "https://javaguide.cn" ,
82253 fallBackImage : "https://javaguide.cn/logo.png" ,
83- customHead : ( head , page ) => {
254+ ogp : ( ogp , page , app ) => ( {
255+ ...ogp ,
256+ "og:description" : buildSeoDescription ( page , app ) ,
257+ } ) ,
258+ jsonLd : ( jsonLD , page , app ) => ( {
259+ ...jsonLD ,
260+ description : buildSeoDescription ( page , app ) ,
261+ } ) ,
262+ customHead : ( head , page , app ) => {
263+ page . frontmatter . description = buildSeoDescription ( page , app ) ;
264+
84265 if ( page . path === "/" )
85266 head . push ( [
86267 "script" ,
0 commit comments