mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
add: sitemap
This commit is contained in:
@@ -148,3 +148,56 @@ func (gs *GlobalScheduler) UpdateSchedulerStatusWithAutoTransfer(autoFetchHotDra
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// StartSitemapScheduler 启动Sitemap调度任务
|
||||
func (gs *GlobalScheduler) StartSitemapScheduler() {
|
||||
gs.mutex.Lock()
|
||||
defer gs.mutex.Unlock()
|
||||
|
||||
if gs.manager.IsSitemapRunning() {
|
||||
utils.Debug("Sitemap定时任务已在运行中")
|
||||
return
|
||||
}
|
||||
|
||||
gs.manager.StartSitemapScheduler()
|
||||
utils.Debug("全局调度器已启动Sitemap定时任务")
|
||||
}
|
||||
|
||||
// StopSitemapScheduler 停止Sitemap调度任务
|
||||
func (gs *GlobalScheduler) StopSitemapScheduler() {
|
||||
gs.mutex.Lock()
|
||||
defer gs.mutex.Unlock()
|
||||
|
||||
if !gs.manager.IsSitemapRunning() {
|
||||
utils.Debug("Sitemap定时任务未在运行")
|
||||
return
|
||||
}
|
||||
|
||||
gs.manager.StopSitemapScheduler()
|
||||
utils.Debug("全局调度器已停止Sitemap定时任务")
|
||||
}
|
||||
|
||||
// IsSitemapSchedulerRunning 检查Sitemap定时任务是否在运行
|
||||
func (gs *GlobalScheduler) IsSitemapSchedulerRunning() bool {
|
||||
gs.mutex.RLock()
|
||||
defer gs.mutex.RUnlock()
|
||||
return gs.manager.IsSitemapRunning()
|
||||
}
|
||||
|
||||
// UpdateSitemapConfig 更新Sitemap配置
|
||||
func (gs *GlobalScheduler) UpdateSitemapConfig(enabled bool) error {
|
||||
return gs.manager.UpdateSitemapConfig(enabled)
|
||||
}
|
||||
|
||||
// GetSitemapConfig 获取Sitemap配置
|
||||
func (gs *GlobalScheduler) GetSitemapConfig() (bool, error) {
|
||||
return gs.manager.GetSitemapConfig()
|
||||
}
|
||||
|
||||
// TriggerSitemapGeneration 手动触发sitemap生成
|
||||
func (gs *GlobalScheduler) TriggerSitemapGeneration() {
|
||||
gs.mutex.Lock()
|
||||
defer gs.mutex.Unlock()
|
||||
|
||||
gs.manager.TriggerSitemapGeneration()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ type Manager struct {
|
||||
baseScheduler *BaseScheduler
|
||||
hotDramaScheduler *HotDramaScheduler
|
||||
readyResourceScheduler *ReadyResourceScheduler
|
||||
sitemapScheduler *SitemapScheduler
|
||||
}
|
||||
|
||||
// NewManager 创建调度器管理器
|
||||
@@ -38,11 +39,13 @@ func NewManager(
|
||||
// 创建各个具体的调度器
|
||||
hotDramaScheduler := NewHotDramaScheduler(baseScheduler)
|
||||
readyResourceScheduler := NewReadyResourceScheduler(baseScheduler)
|
||||
sitemapScheduler := NewSitemapScheduler(baseScheduler)
|
||||
|
||||
return &Manager{
|
||||
baseScheduler: baseScheduler,
|
||||
hotDramaScheduler: hotDramaScheduler,
|
||||
readyResourceScheduler: readyResourceScheduler,
|
||||
sitemapScheduler: sitemapScheduler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,10 +110,41 @@ func (m *Manager) GetHotDramaNames() ([]string, error) {
|
||||
return m.hotDramaScheduler.GetHotDramaNames()
|
||||
}
|
||||
|
||||
// StartSitemapScheduler 启动Sitemap调度任务
|
||||
func (m *Manager) StartSitemapScheduler() {
|
||||
m.sitemapScheduler.Start()
|
||||
}
|
||||
|
||||
// StopSitemapScheduler 停止Sitemap调度任务
|
||||
func (m *Manager) StopSitemapScheduler() {
|
||||
m.sitemapScheduler.Stop()
|
||||
}
|
||||
|
||||
// IsSitemapRunning 检查Sitemap调度任务是否在运行
|
||||
func (m *Manager) IsSitemapRunning() bool {
|
||||
return m.sitemapScheduler.IsRunning()
|
||||
}
|
||||
|
||||
// GetSitemapConfig 获取Sitemap配置
|
||||
func (m *Manager) GetSitemapConfig() (bool, error) {
|
||||
return m.sitemapScheduler.GetSitemapConfig()
|
||||
}
|
||||
|
||||
// UpdateSitemapConfig 更新Sitemap配置
|
||||
func (m *Manager) UpdateSitemapConfig(enabled bool) error {
|
||||
return m.sitemapScheduler.UpdateSitemapConfig(enabled)
|
||||
}
|
||||
|
||||
// TriggerSitemapGeneration 手动触发sitemap生成
|
||||
func (m *Manager) TriggerSitemapGeneration() {
|
||||
go m.sitemapScheduler.generateSitemap()
|
||||
}
|
||||
|
||||
// GetStatus 获取所有调度任务的状态
|
||||
func (m *Manager) GetStatus() map[string]bool {
|
||||
return map[string]bool{
|
||||
"hot_drama": m.IsHotDramaRunning(),
|
||||
"ready_resource": m.IsReadyResourceRunning(),
|
||||
"sitemap": m.IsSitemapRunning(),
|
||||
}
|
||||
}
|
||||
|
||||
308
scheduler/sitemap.go
Normal file
308
scheduler/sitemap.go
Normal file
@@ -0,0 +1,308 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
SITEMAP_MAX_URLS = 50000 // 每个sitemap最多5万个URL
|
||||
SITEMAP_DIR = "./data/sitemap" // sitemap文件目录
|
||||
)
|
||||
|
||||
// SitemapScheduler Sitemap调度器
|
||||
type SitemapScheduler struct {
|
||||
*BaseScheduler
|
||||
sitemapConfig entity.SystemConfig
|
||||
stopChan chan bool
|
||||
isRunning bool
|
||||
}
|
||||
|
||||
// NewSitemapScheduler 创建Sitemap调度器
|
||||
func NewSitemapScheduler(baseScheduler *BaseScheduler) *SitemapScheduler {
|
||||
return &SitemapScheduler{
|
||||
BaseScheduler: baseScheduler,
|
||||
stopChan: make(chan bool),
|
||||
isRunning: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动Sitemap调度任务
|
||||
func (s *SitemapScheduler) Start() {
|
||||
if s.IsRunning() {
|
||||
utils.Debug("Sitemap定时任务已在运行中")
|
||||
return
|
||||
}
|
||||
|
||||
s.SetRunning(true)
|
||||
utils.Info("开始启动Sitemap定时任务")
|
||||
|
||||
go s.run()
|
||||
}
|
||||
|
||||
// Stop 停止Sitemap调度任务
|
||||
func (s *SitemapScheduler) Stop() {
|
||||
if !s.IsRunning() {
|
||||
utils.Debug("Sitemap定时任务未在运行")
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("正在停止Sitemap定时任务")
|
||||
s.stopChan <- true
|
||||
s.SetRunning(false)
|
||||
}
|
||||
|
||||
// IsRunning 检查Sitemap调度任务是否在运行
|
||||
func (s *SitemapScheduler) IsRunning() bool {
|
||||
return s.isRunning
|
||||
}
|
||||
|
||||
// SetRunning 设置运行状态
|
||||
func (s *SitemapScheduler) SetRunning(running bool) {
|
||||
s.isRunning = running
|
||||
}
|
||||
|
||||
// GetStopChan 获取停止通道
|
||||
func (s *SitemapScheduler) GetStopChan() chan bool {
|
||||
return s.stopChan
|
||||
}
|
||||
|
||||
// run 执行调度任务的主循环
|
||||
func (s *SitemapScheduler) run() {
|
||||
utils.Info("Sitemap定时任务开始运行")
|
||||
|
||||
// 立即执行一次
|
||||
s.generateSitemap()
|
||||
|
||||
// 定时执行
|
||||
ticker := time.NewTicker(24 * time.Hour) // 每24小时执行一次
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
utils.Info("定时执行Sitemap生成任务")
|
||||
s.generateSitemap()
|
||||
case <-s.stopChan:
|
||||
utils.Info("收到停止信号,Sitemap调度任务退出")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generateSitemap 生成sitemap
|
||||
func (s *SitemapScheduler) generateSitemap() {
|
||||
utils.Info("开始生成Sitemap...")
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// 获取资源总数
|
||||
var total int64
|
||||
if err := s.BaseScheduler.resourceRepo.GetDB().Model(&entity.Resource{}).Count(&total).Error; err != nil {
|
||||
utils.Error("获取资源总数失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("需要处理的资源总数: %d", total)
|
||||
|
||||
if total == 0 {
|
||||
utils.Info("没有资源需要生成Sitemap")
|
||||
return
|
||||
}
|
||||
|
||||
// 计算需要多少个sitemap文件
|
||||
totalPages := int((total + SITEMAP_MAX_URLS - 1) / SITEMAP_MAX_URLS)
|
||||
utils.Info("需要生成 %d 个sitemap文件", totalPages)
|
||||
|
||||
// 确保目录存在
|
||||
if err := os.MkdirAll(SITEMAP_DIR, 0755); err != nil {
|
||||
utils.Error("创建sitemap目录失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成每个sitemap文件
|
||||
for page := 0; page < totalPages; page++ {
|
||||
if s.SleepWithStopCheck(100 * time.Millisecond) { // 避免过于频繁的检查
|
||||
utils.Info("在生成sitemap过程中收到停止信号,退出生成")
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("正在生成第 %d 个sitemap文件", page+1)
|
||||
|
||||
if err := s.generateSitemapPage(page); err != nil {
|
||||
utils.Error("生成第 %d 个sitemap文件失败: %v", page, err)
|
||||
} else {
|
||||
utils.Info("成功生成第 %d 个sitemap文件", page+1)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成sitemap索引文件
|
||||
if err := s.generateSitemapIndex(totalPages); err != nil {
|
||||
utils.Error("生成sitemap索引文件失败: %v", err)
|
||||
} else {
|
||||
utils.Info("成功生成sitemap索引文件")
|
||||
}
|
||||
|
||||
// 尝试获取网站基础URL
|
||||
baseURL, err := s.BaseScheduler.systemConfigRepo.GetConfigValue(entity.ConfigKeyWebsiteURL)
|
||||
if err != nil || baseURL == "" {
|
||||
baseURL = "https://yoursite.com" // 默认值
|
||||
}
|
||||
|
||||
utils.Info("Sitemap生成完成,耗时: %v", time.Since(startTime))
|
||||
utils.Info("Sitemap地址: %s/sitemap.xml", baseURL)
|
||||
}
|
||||
|
||||
// generateSitemapPage 生成单个sitemap页面
|
||||
func (s *SitemapScheduler) generateSitemapPage(page int) error {
|
||||
offset := page * SITEMAP_MAX_URLS
|
||||
limit := SITEMAP_MAX_URLS
|
||||
|
||||
var resources []entity.Resource
|
||||
if err := s.BaseScheduler.resourceRepo.GetDB().Offset(offset).Limit(limit).Find(&resources).Error; err != nil {
|
||||
return fmt.Errorf("获取资源数据失败: %w", err)
|
||||
}
|
||||
|
||||
var urls []Url
|
||||
for _, resource := range resources {
|
||||
lastMod := resource.UpdatedAt
|
||||
if resource.CreatedAt.After(lastMod) {
|
||||
lastMod = resource.CreatedAt
|
||||
}
|
||||
|
||||
urls = append(urls, Url{
|
||||
Loc: fmt.Sprintf("/r/%s", resource.Key),
|
||||
LastMod: lastMod.Format("2006-01-02"), // 只保留日期部分
|
||||
ChangeFreq: "weekly",
|
||||
Priority: 0.8,
|
||||
})
|
||||
}
|
||||
|
||||
urlSet := UrlSet{
|
||||
XMLNS: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
URLs: urls,
|
||||
}
|
||||
|
||||
filename := filepath.Join(SITEMAP_DIR, fmt.Sprintf("sitemap-%d.xml", page))
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建文件失败: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
file.WriteString(xml.Header)
|
||||
encoder := xml.NewEncoder(file)
|
||||
encoder.Indent("", " ")
|
||||
if err := encoder.Encode(urlSet); err != nil {
|
||||
return fmt.Errorf("写入XML失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateSitemapIndex 生成sitemap索引文件
|
||||
func (s *SitemapScheduler) generateSitemapIndex(totalPages int) error {
|
||||
// 构建主机URL - 这里使用默认URL,实际应用中应从配置获取
|
||||
baseURL, err := s.BaseScheduler.systemConfigRepo.GetConfigValue(entity.ConfigKeyWebsiteURL)
|
||||
if err != nil || baseURL == "" {
|
||||
baseURL = "https://yoursite.com" // 默认值
|
||||
}
|
||||
|
||||
// 移除URL末尾的斜杠
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
var sitemaps []Sitemap
|
||||
for i := 0; i < totalPages; i++ {
|
||||
sitemapURL := fmt.Sprintf("%s/sitemap-%d.xml", baseURL, i)
|
||||
sitemaps = append(sitemaps, Sitemap{
|
||||
Loc: sitemapURL,
|
||||
LastMod: time.Now().Format("2006-01-02"),
|
||||
})
|
||||
}
|
||||
|
||||
sitemapIndex := SitemapIndex{
|
||||
XMLNS: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
Sitemaps: sitemaps,
|
||||
}
|
||||
|
||||
filename := filepath.Join(SITEMAP_DIR, "sitemap.xml")
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建索引文件失败: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
file.WriteString(xml.Header)
|
||||
encoder := xml.NewEncoder(file)
|
||||
encoder.Indent("", " ")
|
||||
if err := encoder.Encode(sitemapIndex); err != nil {
|
||||
return fmt.Errorf("写入索引XML失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSitemapConfig 获取Sitemap配置
|
||||
func (s *SitemapScheduler) GetSitemapConfig() (bool, error) {
|
||||
configStr, err := s.BaseScheduler.systemConfigRepo.GetConfigValue(entity.ConfigKeySitemapConfig)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 解析配置字符串,这里简化处理
|
||||
return configStr == "1" || configStr == "true", nil
|
||||
}
|
||||
|
||||
// UpdateSitemapConfig 更新Sitemap配置
|
||||
func (s *SitemapScheduler) UpdateSitemapConfig(enabled bool) error {
|
||||
configStr := "0"
|
||||
if enabled {
|
||||
configStr = "1"
|
||||
}
|
||||
|
||||
config := entity.SystemConfig{
|
||||
Key: entity.ConfigKeySitemapConfig,
|
||||
Value: configStr,
|
||||
Type: "bool",
|
||||
}
|
||||
|
||||
// 由于repository没有直接的SetConfig方法,我们使用UpsertConfigs
|
||||
configs := []entity.SystemConfig{config}
|
||||
return s.BaseScheduler.systemConfigRepo.UpsertConfigs(configs)
|
||||
}
|
||||
|
||||
// UrlSet sitemap内容
|
||||
type UrlSet struct {
|
||||
XMLName xml.Name `xml:"urlset"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
URLs []Url `xml:"url"`
|
||||
}
|
||||
|
||||
// Url 单个URL信息
|
||||
type Url struct {
|
||||
Loc string `xml:"loc"`
|
||||
LastMod string `xml:"lastmod"`
|
||||
ChangeFreq string `xml:"changefreq"`
|
||||
Priority float64 `xml:"priority"`
|
||||
}
|
||||
|
||||
// SitemapIndex sitemap索引结构
|
||||
type SitemapIndex struct {
|
||||
XMLName xml.Name `xml:"sitemapindex"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
Sitemaps []Sitemap `xml:"sitemap"`
|
||||
}
|
||||
|
||||
// Sitemap 单个sitemap信息
|
||||
type Sitemap struct {
|
||||
Loc string `xml:"loc"`
|
||||
LastMod string `xml:"lastmod"`
|
||||
}
|
||||
Reference in New Issue
Block a user