diff --git a/api/root.go b/api/root.go index 33da92b..dda84c8 100644 --- a/api/root.go +++ b/api/root.go @@ -1,9 +1,12 @@ package api import ( + "fmt" "net/http" "rankland/errcode" "rankland/logic" + "strconv" + "strings" "github.com/gin-gonic/gin" ) @@ -21,6 +24,46 @@ func GetStatistics(c *gin.Context) { }) } +func SitemapRanklistIndex(c *gin.Context) { + volCnt, err := logic.GetRankSitemapVolCount() + if err != nil { + c.Errors = append(c.Errors, errcode.ServerErr) + return + } + + vols := make([]string, 0, volCnt) + for i := int32(1); i <= volCnt; i++ { + vols = append(vols, fmt.Sprintf("https://rl.algoux.org/sitemap/ranklist_vol_%d.txt", i)) + } + + c.XML(http.StatusOK, sitemapindex{ + Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9", + Values: vols, + }) +} + +func SitemapRanklistVol(c *gin.Context) { + volName := c.Param("volName") + idx, err := strconv.ParseInt(strings.TrimSuffix(volName, ".txt"), 10, 32) + if err != nil || idx <= 0 { + c.Errors = append(c.Errors, errcode.ParamErr) + return + } + + uniqueKeys, err := logic.GetRankSitemapVol(int(idx)) + if err != nil { + c.Errors = append(c.Errors, errcode.ServerErr) + return + } + + text := "" + for _, key := range uniqueKeys { + text += fmt.Sprintf("https://rl.algoux.org/ranklist/%s\n", key) + } + + c.String(http.StatusOK, text) +} + type Resp struct { Code int32 `json:"code"` Message string `json:"message"` @@ -34,3 +77,8 @@ func statusOk(c *gin.Context, v interface{}) { Data: v, }) } + +type sitemapindex struct { + Xmlns string `xml:"xmlns,attr"` + Values []string `xml:"sitemap>loc"` +} diff --git a/api/router/router.go b/api/router/router.go index 775a68e..175c7c8 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -18,6 +18,8 @@ func Group(r *gin.Engine) { }) r.GET("/statistics", api.GetStatistics) + r.GET("/sitemap/ranklist.xml", api.SitemapRanklistIndex) + r.GET("/sitemap/ranklist_vol_:volName", api.SitemapRanklistVol) rank(r.Group("/rank")) ranking(r.Group("/ranking")) diff --git a/logic/sitemap.go b/logic/sitemap.go new file mode 100644 index 0000000..5d3d1b7 --- /dev/null +++ b/logic/sitemap.go @@ -0,0 +1,31 @@ +package logic + +import ( + "rankland/model/rank" +) + +const ( + SitemapVolCap = 1000 +) + +func GetRankSitemapVolCount() (volCnt int32, err error) { + rankCnt, err := rank.GetRankCntStatistics() + if err != nil { + return 0, err + } + + return (rankCnt + SitemapVolCap - 1) / SitemapVolCap, nil +} + +func GetRankSitemapVol(volIdx int) (uniqueKeys []string, err error) { + if volIdx < 1 { + return []string{}, nil + } + offset := (volIdx - 1) * SitemapVolCap + uniqueKeys, err = rank.GetAllRankUniqueKeys(offset, SitemapVolCap) + if err != nil { + return []string{}, err + } + + return uniqueKeys, nil +} diff --git a/main.go b/main.go index 528ad65..a6fa574 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ func main() { r.Use( middleware.Cors(app.Cors), // 启用跨域拦截 middleware.Error(), // 启用 Error 处理 + middleware.XMLHeader(), // 启用 XML Header ) router.Group(r) diff --git a/middleware/xml.go b/middleware/xml.go new file mode 100644 index 0000000..f825681 --- /dev/null +++ b/middleware/xml.go @@ -0,0 +1,19 @@ +package middleware + +import ( + "encoding/xml" + "strings" + + "github.com/gin-gonic/gin" +) + +// XMLHeader +func XMLHeader() gin.HandlerFunc { + return func(ctx *gin.Context) { + if strings.HasSuffix(ctx.Request.URL.Path, ".xml") && ctx.NegotiateFormat(gin.MIMEXML) == gin.MIMEXML { + ctx.Writer.Write([]byte(xml.Header)) + } + + ctx.Next() + } +} diff --git a/model/rank/action.go b/model/rank/action.go index 1b92402..ca49f78 100644 --- a/model/rank/action.go +++ b/model/rank/action.go @@ -90,6 +90,41 @@ func GetRankStatistics() (rankCnt, ViewCnt int32, err error) { return rs.RankCnt, rs.ViewCnt, nil } +type RankCntStatistics struct { + RankCnt int32 +} + +func GetRankCntStatistics() (rankCnt int32, err error) { + rs := RankCntStatistics{} + db := load.GetDB().Model(&Rank{}) + sql := db.Select("count(distinct unique_key) as rank_cnt").Find(&rs) + if sql.Error != nil { + return 0, sql.Error + } + + return rs.RankCnt, nil +} + +type RankOnlyUniqueKey struct { + UniqueKey string +} + +func GetAllRankUniqueKeys(offset int, limit int) (uniqueKey []string, err error) { + rs := []RankOnlyUniqueKey{} + db := load.GetDB().Model(&Rank{}) + sql := db.Distinct("unique_key").Offset(offset).Limit(limit).Find(&rs) + if sql.Error != nil { + return []string{}, sql.Error + } + + uniqueKeys := make([]string, 0, len(rs)) + for _, r := range rs { + uniqueKeys = append(uniqueKeys, r.UniqueKey) + } + + return uniqueKeys, nil +} + func GetRankGroupByID(id int64) (*RankGroup, error) { rg := &RankGroup{} db := load.GetDB().Where("id = ?", id)