数据源:百度百科开放接口 BaikeLemmaCardApi(无反爬,返回结构化 JSON) 与「历史上的今天」(维基)完全解耦,维基代码原样保留可随时恢复: 服务端: - WikiEntry 增加 image(配图)和 extra(结构化字段 JSON)字段 - 新建 BaikeTermSyncLog 独立日志表(不污染维基 WikiSyncLog) - 新建 BaikeTermSyncService 独立同步服务(24 节气,间隔 1.2s) - 启动时缺详情自动补齐,之后按配置间隔定时增量刷新 - 管理 API:GET/POST /admin/api/wiki/term-sync* 管理后台: - 百科条目 Tab 顶部加节气同步卡片(X/24 + 状态 + 同步按钮) - 条目编辑器加配图 URL(含预览)和节气扩展字段编辑 小程序: - 百科页展开详情图文展示:配图 + 简介 + 物候/习俗/养生等分块 本地验证:24/24 节气抓取成功,image/extra 全部填充,维基数据不受影响
131 lines
4.6 KiB
Go
131 lines
4.6 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// 取自百度百科 BaikeLemmaCardApi 「立春」的真实返回片段
|
|
const sampleLemmaJSON = `{
|
|
"id":25702,"key":"立春","desc":"二十四节气之一","title":"立春",
|
|
"image":"https://bkimg.cdn.bcebos.com/pic/d62a6059252dd42a2834edd463674cb5c9ea15ce1205",
|
|
"abstract":"立春(Beginning of Spring),又名正月节、岁节、改岁、岁旦、打春等,为二十四节气之首。当太阳到达黄经315°时为立春,于每年公历2月3-5日交节。",
|
|
"card":[
|
|
{"key":"m25_nameC","name":"中文名","value":["立春"]},
|
|
{"key":"m25_nameE","name":"外文名","value":["Beginning of Spring<sup>3</sup><a name=\"ref_3\"></a>","Lichun"]},
|
|
{"key":"m25_alternateName","name":"别名","value":["岁首、岁节、改岁、立春节、正月节"]},
|
|
{"key":"m25_meaning","name":"涵义","value":["阳气上升,万物更生,新岁开启"]},
|
|
{"key":"m25_solarDate","name":"公历时间","value":["公历2月3日-2月5日交节"]},
|
|
{"key":"m25_ecliptic","name":"黄道位置","value":["太阳到达黄经315度"]},
|
|
{"key":"m25_phenology","name":"物候现象","value":["东风解冻、蛰虫始振、鱼陟负冰"]},
|
|
{"key":"m25_farming","name":"农事活动","value":["备耕春播"]},
|
|
{"key":"m25_customs","name":"传统习俗","value":["<a target=_blank href=\"/item/x\">演春</a>、迎春、打春"]},
|
|
{"key":"m25_health","name":"起居养生","value":["防躁动,避免过度劳累,保证睡眠情绪豁达。"]},
|
|
{"key":"m25_flower","name":"花信","value":["迎春、樱桃、望春<sup>35</sup><a name=\"ref_35\"></a>"]}
|
|
]
|
|
}`
|
|
|
|
func TestParseLemmaCard(t *testing.T) {
|
|
var lemma baikeLemmaCard
|
|
if err := json.Unmarshal([]byte(sampleLemmaJSON), &lemma); err != nil {
|
|
t.Fatalf("JSON 解析失败: %v", err)
|
|
}
|
|
if lemma.Key != "立春" {
|
|
t.Errorf("key = %q, want 立春", lemma.Key)
|
|
}
|
|
if lemma.Image == "" {
|
|
t.Error("image 不应为空")
|
|
}
|
|
if !strings.Contains(lemma.Abstract, "二十四节气之首") {
|
|
t.Errorf("abstract 应含「二十四节气之首」, got %q", lemma.Abstract)
|
|
}
|
|
if len(lemma.Card) == 0 {
|
|
t.Fatal("card 不应为空")
|
|
}
|
|
}
|
|
|
|
func TestLemmaCardMapping(t *testing.T) {
|
|
var lemma baikeLemmaCard
|
|
json.Unmarshal([]byte(sampleLemmaJSON), &lemma)
|
|
|
|
extra := map[string]string{}
|
|
for _, c := range lemma.Card {
|
|
text := cleanTermHTML(firstValue(c.Value))
|
|
if text == "" {
|
|
continue
|
|
}
|
|
switch c.Name {
|
|
case "别名":
|
|
extra["alias"] = text
|
|
case "外文名":
|
|
extra["english"] = text
|
|
case "涵义":
|
|
extra["meaning"] = text
|
|
case "物候现象":
|
|
extra["phenology"] = text
|
|
case "传统习俗":
|
|
extra["customs"] = text
|
|
case "起居养生":
|
|
extra["health"] = text
|
|
}
|
|
}
|
|
|
|
if extra["alias"] != "岁首、岁节、改岁、立春节、正月节" {
|
|
t.Errorf("alias = %q", extra["alias"])
|
|
}
|
|
if extra["meaning"] != "阳气上升,万物更生,新岁开启" {
|
|
t.Errorf("meaning = %q", extra["meaning"])
|
|
}
|
|
if extra["phenology"] != "东风解冻、蛰虫始振、鱼陟负冰" {
|
|
t.Errorf("phenology = %q", extra["phenology"])
|
|
}
|
|
// 传统习俗含 HTML 链接,应被清洗
|
|
if strings.Contains(extra["customs"], "<a") || strings.Contains(extra["customs"], "href") {
|
|
t.Errorf("customs 应清洗 HTML, got %q", extra["customs"])
|
|
}
|
|
if !strings.Contains(extra["customs"], "演春") {
|
|
t.Errorf("customs 应保留文字「演春」, got %q", extra["customs"])
|
|
}
|
|
// 外文名含 <sup> 应清洗
|
|
if strings.Contains(extra["english"], "<sup>") {
|
|
t.Errorf("english 应清洗 sup 标签, got %q", extra["english"])
|
|
}
|
|
}
|
|
|
|
func TestCleanTermHTML(t *testing.T) {
|
|
cases := map[string]string{
|
|
`<a target=_blank href="/item/x">演春</a>、迎春`: "演春、迎春",
|
|
`Beginning of Spring<sup>3</sup><a name="ref_3"></a>`: "Beginning of Spring",
|
|
` 多余 空白 `: "多余 空白",
|
|
}
|
|
for in, want := range cases {
|
|
if got := cleanTermHTML(in); got != want {
|
|
t.Errorf("cleanTermHTML(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFirstSentence(t *testing.T) {
|
|
s := "立春为二十四节气之首。当太阳到达黄经315°时为立春。"
|
|
if got := firstSentence(s); got != "立春为二十四节气之首。" {
|
|
t.Errorf("firstSentence = %q", got)
|
|
}
|
|
// 无句号时返回原文
|
|
if got := firstSentence("无句号文本"); got != "无句号文本" {
|
|
t.Errorf("firstSentence 无句号 = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestTermSort(t *testing.T) {
|
|
if termSort("立春") != 1 {
|
|
t.Errorf("termSort(立春) = %d, want 1", termSort("立春"))
|
|
}
|
|
if termSort("大寒") != 24 {
|
|
t.Errorf("termSort(大寒) = %d, want 24", termSort("大寒"))
|
|
}
|
|
if termSort("不存在") != 0 {
|
|
t.Errorf("termSort(不存在) = %d, want 0", termSort("不存在"))
|
|
}
|
|
}
|