Files
urldb/handlers/sitemap_handler.go
2025-11-21 01:47:02 +08:00

411 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handlers
import (
"encoding/xml"
"fmt"
"net/http"
"strconv"
"time"
"github.com/ctwj/urldb/db/entity"
"github.com/ctwj/urldb/db/repo"
"github.com/ctwj/urldb/scheduler"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var (
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
hotDramaRepo repo.HotDramaRepository
readyResourceRepo repo.ReadyResourceRepository
panRepo repo.PanRepository
cksRepo repo.CksRepository
tagRepo repo.TagRepository
categoryRepo repo.CategoryRepository
)
// SetSitemapDependencies 注册Sitemap处理器依赖
func SetSitemapDependencies(
resourceRepository repo.ResourceRepository,
systemConfigRepository repo.SystemConfigRepository,
hotDramaRepository repo.HotDramaRepository,
readyResourceRepository repo.ReadyResourceRepository,
panRepository repo.PanRepository,
cksRepository repo.CksRepository,
tagRepository repo.TagRepository,
categoryRepository repo.CategoryRepository,
) {
resourceRepo = resourceRepository
systemConfigRepo = systemConfigRepository
hotDramaRepo = hotDramaRepository
readyResourceRepo = readyResourceRepository
panRepo = panRepository
cksRepo = cksRepository
tagRepo = tagRepository
categoryRepo = categoryRepository
}
const SITEMAP_MAX_URLS = 50000 // 每个sitemap最多5万个URL
// 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"`
}
// 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"`
}
// SitemapConfig sitemap配置
type SitemapConfig struct {
AutoGenerate bool `json:"auto_generate"`
LastGenerate time.Time `json:"last_generate"`
LastUpdate time.Time `json:"last_update"`
}
// GetSitemapConfig 获取sitemap配置
func GetSitemapConfig(c *gin.Context) {
// 从全局调度器获取配置
enabled, err := scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
).GetSitemapConfig()
if err != nil && err != gorm.ErrRecordNotFound {
// 如果获取失败,尝试从配置表中获取
configStr, err := systemConfigRepo.GetConfigValue(entity.ConfigKeySitemapAutoGenerateEnabled)
if err != nil && err != gorm.ErrRecordNotFound {
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
return
}
enabled = configStr == "1" || configStr == "true"
}
// 获取最后生成时间(从配置中获取)
configStr, err := systemConfigRepo.GetConfigValue(entity.ConfigKeySitemapLastGenerateTime)
if err != nil && err != gorm.ErrRecordNotFound {
// 如果获取失败,只返回启用状态
config := SitemapConfig{
AutoGenerate: enabled,
LastGenerate: time.Time{}, // 空时间
LastUpdate: time.Now(),
}
SuccessResponse(c, config)
return
}
var lastGenerateTime time.Time
if configStr != "" {
lastGenerateTime, _ = time.Parse("2006-01-02 15:04:05", configStr)
}
config := SitemapConfig{
AutoGenerate: enabled,
LastGenerate: lastGenerateTime,
LastUpdate: time.Now(),
}
SuccessResponse(c, config)
}
// UpdateSitemapConfig 更新sitemap配置
func UpdateSitemapConfig(c *gin.Context) {
var config SitemapConfig
if err := c.ShouldBindJSON(&config); err != nil {
ErrorResponse(c, "参数错误", http.StatusBadRequest)
return
}
// 更新调度器配置
if err := scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
).UpdateSitemapConfig(config.AutoGenerate); err != nil {
ErrorResponse(c, "更新调度器配置失败", http.StatusInternalServerError)
return
}
// 保存自动生成功能状态
autoGenerateStr := "0"
if config.AutoGenerate {
autoGenerateStr = "1"
}
autoGenerateConfig := entity.SystemConfig{
Key: entity.ConfigKeySitemapAutoGenerateEnabled,
Value: autoGenerateStr,
Type: "bool",
}
// 保存最后生成时间
lastGenerateStr := config.LastGenerate.Format("2006-01-02 15:04:05")
lastGenerateConfig := entity.SystemConfig{
Key: entity.ConfigKeySitemapLastGenerateTime,
Value: lastGenerateStr,
Type: "string",
}
configs := []entity.SystemConfig{autoGenerateConfig, lastGenerateConfig}
if err := systemConfigRepo.UpsertConfigs(configs); err != nil {
ErrorResponse(c, "保存配置失败", http.StatusInternalServerError)
return
}
// 根据配置启动或停止调度器
if config.AutoGenerate {
scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
).StartSitemapScheduler()
} else {
scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
).StopSitemapScheduler()
}
SuccessResponse(c, config)
}
// GenerateSitemap 手动生成sitemap
func GenerateSitemap(c *gin.Context) {
// 获取资源总数
var total int64
if err := resourceRepo.GetDB().Model(&entity.Resource{}).Count(&total).Error; err != nil {
ErrorResponse(c, "获取资源总数失败", http.StatusInternalServerError)
return
}
totalPages := int((total + SITEMAP_MAX_URLS - 1) / SITEMAP_MAX_URLS)
// 获取全局调度器并立即执行sitemap生成
globalScheduler := scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
)
// 手动触发sitemap生成
globalScheduler.TriggerSitemapGeneration()
// 记录最后生成时间为当前时间
lastGenerateStr := time.Now().Format("2006-01-02 15:04:05")
lastGenerateConfig := entity.SystemConfig{
Key: entity.ConfigKeySitemapLastGenerateTime,
Value: lastGenerateStr,
Type: "string",
}
if err := systemConfigRepo.UpsertConfigs([]entity.SystemConfig{lastGenerateConfig}); err != nil {
ErrorResponse(c, "更新最后生成时间失败", http.StatusInternalServerError)
return
}
result := map[string]interface{}{
"total_resources": total,
"total_pages": totalPages,
"status": "started",
"message": fmt.Sprintf("开始生成 %d 个sitemap文件", totalPages),
}
SuccessResponse(c, result)
}
// GetSitemapStatus 获取sitemap生成状态
func GetSitemapStatus(c *gin.Context) {
// 获取资源总数
var total int64
if err := resourceRepo.GetDB().Model(&entity.Resource{}).Count(&total).Error; err != nil {
ErrorResponse(c, "获取资源总数失败", http.StatusInternalServerError)
return
}
// 计算需要生成的sitemap文件数量
totalPages := int((total + SITEMAP_MAX_URLS - 1) / SITEMAP_MAX_URLS)
// 获取最后生成时间
lastGenerateStr, err := systemConfigRepo.GetConfigValue(entity.ConfigKeySitemapLastGenerateTime)
if err != nil {
// 如果没有记录,使用当前时间
lastGenerateStr = time.Now().Format("2006-01-02 15:04:05")
}
lastGenerate, err := time.Parse("2006-01-02 15:04:05", lastGenerateStr)
if err != nil {
lastGenerate = time.Now()
}
// 检查调度器是否运行
isRunning := scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
).IsSitemapSchedulerRunning()
// 获取自动生成功能状态
autoGenerateEnabled, err := scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
).GetSitemapConfig()
if err != nil {
// 如果调度器获取失败,从配置中获取
configStr, err := systemConfigRepo.GetConfigValue(entity.ConfigKeySitemapAutoGenerateEnabled)
if err != nil {
autoGenerateEnabled = false
} else {
autoGenerateEnabled = configStr == "1" || configStr == "true"
}
}
result := map[string]interface{}{
"total_resources": total,
"total_pages": totalPages,
"last_generate": lastGenerate.Format("2006-01-02 15:04:05"),
"status": "ready",
"is_running": isRunning,
"auto_generate": autoGenerateEnabled,
}
SuccessResponse(c, result)
}
// SitemapIndexHandler sitemap索引文件处理器
func SitemapIndexHandler(c *gin.Context) {
// 获取资源总数
var total int64
if err := resourceRepo.GetDB().Model(&entity.Resource{}).Count(&total).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取资源总数失败"})
return
}
totalPages := int((total + SITEMAP_MAX_URLS - 1) / SITEMAP_MAX_URLS)
// 构建主机URL
scheme := "http"
if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
}
host := c.Request.Host
if host == "" {
host = "localhost:8080" // 默认值
}
baseURL := fmt.Sprintf("%s://%s", scheme, host)
// 创建sitemap列表 - 现在文件保存在data/sitemap目录通过/file/sitemap/路径访问
var sitemaps []Sitemap
for i := 0; i < totalPages; i++ {
sitemapURL := fmt.Sprintf("%s/file/sitemap/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,
}
c.Header("Content-Type", "application/xml")
c.XML(http.StatusOK, sitemapIndex)
}
// SitemapPageHandler sitemap页面处理器
func SitemapPageHandler(c *gin.Context) {
pageStr := c.Param("page")
page, err := strconv.Atoi(pageStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的页面参数"})
return
}
offset := page * SITEMAP_MAX_URLS
limit := SITEMAP_MAX_URLS
var resources []entity.Resource
if err := resourceRepo.GetDB().Offset(offset).Limit(limit).Find(&resources).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取资源数据失败"})
return
}
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-01"), // 只保留日期部分
ChangeFreq: "weekly",
Priority: 0.8,
})
}
urlSet := UrlSet{
XMLNS: "http://www.sitemaps.org/schemas/sitemap/0.9",
URLs: urls,
}
c.Header("Content-Type", "application/xml")
c.XML(http.StatusOK, urlSet)
}
// 手动生成完整sitemap文件
func GenerateFullSitemap(c *gin.Context) {
// 获取资源总数
var total int64
if err := resourceRepo.GetDB().Model(&entity.Resource{}).Count(&total).Error; err != nil {
ErrorResponse(c, "获取资源总数失败", http.StatusInternalServerError)
return
}
// 获取全局调度器并立即执行sitemap生成
globalScheduler := scheduler.GetGlobalScheduler(
hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo,
panRepo, cksRepo, tagRepo, categoryRepo,
)
// 手动触发sitemap生成
globalScheduler.TriggerSitemapGeneration()
// 记录最后生成时间为当前时间
lastGenerateStr := time.Now().Format("2006-01-02 15:04:05")
lastGenerateConfig := entity.SystemConfig{
Key: entity.ConfigKeySitemapLastGenerateTime,
Value: lastGenerateStr,
Type: "string",
}
if err := systemConfigRepo.UpsertConfigs([]entity.SystemConfig{lastGenerateConfig}); err != nil {
ErrorResponse(c, "更新最后生成时间失败", http.StatusInternalServerError)
return
}
result := map[string]interface{}{
"message": "Sitemap生成任务已启动",
"total_resources": total,
"status": "processing",
"estimated_time": fmt.Sprintf("%d秒", total/1000), // 估算时间
}
SuccessResponse(c, result)
}