From e9f1b8b4c30029c0e36017be1e941b2301e80e27 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 16 Oct 2026 16:35:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20IndexNow=20?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=8F=90=E4=BA=A4=E6=8E=A5=E5=8F=A3=EF=BC=88?= =?UTF-8?q?=E4=B8=BB=E9=A1=B5=20+=20=E4=B8=93=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /api/web/seopush/indexnow/bulk?limit=10 - 组装主页 "/" 和前 N-1 个专题(playlet_theme 按 Rank 降序)URL - 调用 pub/seo/indexnow.BatchIndexNowSubmitter 提交到 api.indexnow.org - 返回提交的 URL 列表 + IndexNow 回执(含各引擎 HTTP 状态码) - Admin 鉴权(走 /api/web/* 的 auth 中间件) 部署前提: - web.yaml 配置 indexNow.key - Web 根目录可访问 /{key}.txt(内容为 key 本身)作为 IndexNow 域名验证 Co-Authored-By: Claude Opus 4.7 --- web/api/web_seopush/seopush.go | 31 ++++++++++++++ web/router/gin.go | 1 + web/router/seopush.go | 14 ++++++ web/service/srv_seopush/bulk.go | 75 +++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 web/api/web_seopush/seopush.go create mode 100644 web/router/seopush.go create mode 100644 web/service/srv_seopush/bulk.go diff --git a/web/api/web_seopush/seopush.go b/web/api/web_seopush/seopush.go new file mode 100644 index 0000000..011ca28 --- /dev/null +++ b/web/api/web_seopush/seopush.go @@ -0,0 +1,31 @@ +package web_seopush + +import ( + "strconv" + + "hkdj-web-server/pub/common" + "hkdj-web-server/pub/common/srvdef" + "hkdj-web-server/web/service/srv_seopush" + + "github.com/gin-gonic/gin" +) + +// @Tags SEO推送 +// @Summary IndexNow 批量提交(主页 + 专题) +// @Description 组装主页和前 N 个专题(按 Rank 降序)地址,提交到 IndexNow(Bing / Yandex 等)。 +// @Produce json +// @Param limit query int false "URL 总数,含主页,默认 10" +// @Success 200 {object} srv_seopush.BulkIndexNowResult +// @Router /api/web/seopush/indexnow/bulk [POST] +func SubmitHomeAndThemes(ctx *gin.Context) { + limit, _ := strconv.Atoi(ctx.Query("limit")) + if limit <= 0 { + limit = 10 + } + resp, serr := srv_seopush.SubmitHomeAndThemes(ctx, limit) + if serr != nil { + common.ServerJSON(ctx, serr.Code, serr.Msg) + return + } + common.ServerJSON(ctx, srvdef.Success, resp) +} diff --git a/web/router/gin.go b/web/router/gin.go index 0fd763e..64b285d 100644 --- a/web/router/gin.go +++ b/web/router/gin.go @@ -95,6 +95,7 @@ func registerRouter(router *gin.Engine) { homeConfigRouter(rootRouter) feedbackRouter(rootRouter) contactUsRouter(rootRouter) + seopushRouter(rootRouter) } // 第三方回调路由(免鉴权,挂在 engine 上而非 /api/web 分组) thirdRouter(router) diff --git a/web/router/seopush.go b/web/router/seopush.go new file mode 100644 index 0000000..47692e0 --- /dev/null +++ b/web/router/seopush.go @@ -0,0 +1,14 @@ +package router + +import ( + "hkdj-web-server/web/api/web_seopush" + + "github.com/gin-gonic/gin" +) + +func seopushRouter(root *gin.RouterGroup) { + group := root.Group("/seopush") + { + group.POST("/indexnow/bulk", web_seopush.SubmitHomeAndThemes) + } +} diff --git a/web/service/srv_seopush/bulk.go b/web/service/srv_seopush/bulk.go new file mode 100644 index 0000000..c811e56 --- /dev/null +++ b/web/service/srv_seopush/bulk.go @@ -0,0 +1,75 @@ +package srv_seopush + +import ( + "context" + "fmt" + "strings" + + "hkdj-web-server/pub/common/log" + "hkdj-web-server/pub/common/srvdef" + "hkdj-web-server/pub/models/playletthememod" + "hkdj-web-server/pub/seo/h5path" + seoindexnow "hkdj-web-server/pub/seo/indexnow" + "hkdj-web-server/web/webg" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// BulkIndexNowResult 批量提交结果,包含提交的 URL 列表和 IndexNow 原始回执。 +type BulkIndexNowResult struct { + URLs []string `json:"urls"` + SubmitInfo *seoindexnow.BatchResult `json:"submitInfo"` +} + +// SubmitHomeAndThemes 组装主页 + 前 N 个专题(按 Rank 降序)URL,批量提交 IndexNow。 +// limit 为 URL 总数(包含主页),默认 10;小于等于 1 时视为仅主页。 +func SubmitHomeAndThemes(ctx context.Context, limit int) (*BulkIndexNowResult, *srvdef.SrvErr) { + if limit <= 0 { + limit = 10 + } + webAddr := SEOBaseURL(ctx) + if webAddr == "" { + return nil, srvdef.New(srvdef.ErrNoData, "WebAddr 未配置", "") + } + key := strings.TrimSpace(webg.Conf.IndexNow.Key) + if key == "" { + return nil, srvdef.New(srvdef.ErrNoData, "indexNow.key 未配置", "") + } + + urls := make([]string, 0, limit) + urls = append(urls, h5path.Abs(webAddr, "/")) + if remain := limit - 1; remain > 0 { + themes, err := playletthememod.FindManyPlayletTheme(ctx, + bson.M{playletthememod.FN.PlayletTheme.Status: true}, + options.Find(). + SetSort(bson.M{playletthememod.FN.PlayletTheme.Rank: -1}). + SetLimit(int64(remain))) + if err == nil { + for _, t := range themes { + if t == nil || t.Id <= 0 { + continue + } + path := h5path.TopicDetail(t.Slug, t.Id) + if path == "" { + continue + } + urls = append(urls, h5path.Abs(webAddr, path)) + } + } else { + log.Warn("seopush bulk: list themes failed", log.E(err)) + } + } + + config, cerr := seoindexnow.BuildConfig(webAddr, key) + if cerr != nil { + return nil, srvdef.New(srvdef.Failure, fmt.Sprintf("构造 IndexNow 配置失败: %v", cerr), "") + } + result := seoindexnow.NewBatchIndexNowSubmitter(config).SubmitBatch(urls) + log.Info("seopush bulk: indexnow submitted", + log.Any("urls", urls), + log.Any("statusCodes", result.StatusCodes), + log.Any("success", result.SuccessCount), + log.Any("fail", result.FailCount)) + return &BulkIndexNowResult{URLs: urls, SubmitInfo: result}, nil +}