package seo import ( "context" "path/filepath" "strings" "sync" "time" "videoweb/app/service/configser" "videoweb/app/skd/job/dhsitemap" "videoweb/app/skd/job/llmstxt" "videoweb/app/skd/job/rss" "videoweb/app/skd/job/sitemap" "videoweb/pub/common/log" "videoweb/pub/seo/sitemapdirty" "github.com/robfig/cron/v3" ) const ( // h5OutputDir 站点地图写在 h5 根目录,不进 public。 // 样式表仍由前端 public/static 提供,XML 里引用 /static/sitemap*.xsl。 h5OutputDir = "../h5" pageTemplateOutputDir = "../h5/templates/page" ) var seoCron *cron.Cron func init() { seoCron = cron.New(cron.WithSeconds()) } var ( dirtyMu sync.Mutex // generatedDirtyAt 最近一次 sitemap 生成所对应的内容变更时间 generatedDirtyAt time.Time ) // Start 启动 SEO 静态资源生成(sitemap / RSS / llms.txt / dhsitemap)定时任务。 // 页面级搜索引擎推送由 web 进程的 seopush 任务负责,不在这里处理。 func Start() { generatedDirtyAt = sitemapdirty.LastDirtyAt(context.Background()) go generateSitemapFiles(context.Background()) go dhsitemap.GenerateDHSitemap() go llmstxt.GenerateLLMSTxt(pageTemplateOutputDir) _, _ = seoCron.AddFunc("0 0 */6 * * ?", func() { generateSitemapFiles(context.Background()) dhsitemap.GenerateDHSitemap() llmstxt.GenerateLLMSTxt(pageTemplateOutputDir) }) // 短剧/视频等发布发生在 web 进程,这里检查它留下的变更标记,有变更才重新生成 _, _ = seoCron.AddFunc("0 */2 * * * ?", func() { regenerateSitemapIfDirty() }) seoCron.Start() log.Info("seo skd started") } func generateSitemapFiles(ctx context.Context) { config, serr := configser.GetSystemConfig(ctx) if serr != nil || config == nil { log.Error("gen sitemap GetSystemConfig failed", log.Any("err", serr)) return } webAddr := strings.TrimSpace(config.WebAddr) if webAddr == "" { log.Error("gen sitemap WebAddr empty") return } generator := sitemap.NewSitemapGenerator(webAddr, filepath.Join(h5OutputDir, "sitemaps")) generator.IndexFile = filepath.Join(h5OutputDir, "sitemap.xml") if err := generator.Generate(); err != nil { log.Error("gen sitemap.xml err", log.E(err)) return } rss.GenerateRSS(ctx, pageTemplateOutputDir) } func regenerateSitemapIfDirty() { if !dirtyMu.TryLock() { return } defer dirtyMu.Unlock() dirtyAt := sitemapdirty.LastDirtyAt(context.Background()) if dirtyAt.IsZero() || !dirtyAt.After(generatedDirtyAt) { return } log.Info("seo: content changed, regenerate sitemap", log.Any("dirtyAt", dirtyAt)) generateSitemapFiles(context.Background()) dhsitemap.GenerateDHSitemap() llmstxt.GenerateLLMSTxt(pageTemplateOutputDir) generatedDirtyAt = dirtyAt } func Stop() { if seoCron != nil { seoCron.Stop() } }