package handlers import ( "context" "encoding/json" "net/http" "strings" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "yh_web/server/config" "yh_web/server/models" "github.com/gin-gonic/gin" ) const homepageSlug = "index" const officialSiteConfigID = "official_site_id" type officialSiteDoc struct { ID string `bson:"_id"` SiteID string `bson:"site_id"` } // getOfficialSiteID 从 system_config 读取官网站点 ID;未设置则返回第一个站点的 ID func getOfficialSiteID(ctx context.Context) string { coll := config.GetDB(config.DBName).Collection("system_config") var doc officialSiteDoc err := coll.FindOne(ctx, bson.M{"_id": officialSiteConfigID}).Decode(&doc) if err == nil && doc.SiteID != "" { return doc.SiteID } sitesColl := config.GetDB(config.DBName).Collection("sites") opts := options.FindOne().SetSort(bson.D{{Key: "created_at", Value: -1}}) var site models.Site if err := sitesColl.FindOne(ctx, bson.M{}, opts).Decode(&site); err == nil { return site.ID.Hex() } return "" } // GetWebHomepage 前台:获取官网站点首页数据(无需鉴权) func GetWebHomepage(c *gin.Context) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() siteID := getOfficialSiteID(ctx) if siteID == "" { c.JSON(http.StatusOK, defaultHomepageData()) return } coll := config.GetDB(config.DBName).Collection("pages") var page models.Page err := coll.FindOne(ctx, bson.M{"site_id": siteID, "slug": homepageSlug}).Decode(&page) if err != nil { if err == mongo.ErrNoDocuments { c.JSON(http.StatusOK, defaultHomepageData()) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } var data models.HomepageData if page.Content != "" { if err := json.Unmarshal([]byte(page.Content), &data); err != nil { c.JSON(http.StatusOK, defaultHomepageData()) return } } else { data = defaultHomepageData() } c.JSON(http.StatusOK, data) } // GetHomepage 获取站点首页数据 func GetHomepage(c *gin.Context) { siteID := c.Param("site_id") if siteID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"}) return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() coll := config.GetDB(config.DBName).Collection("pages") var page models.Page err := coll.FindOne(ctx, bson.M{"site_id": siteID, "slug": homepageSlug}).Decode(&page) if err != nil { if err == mongo.ErrNoDocuments { c.JSON(http.StatusOK, defaultHomepageData()) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } var data models.HomepageData if page.Content != "" { if err := json.Unmarshal([]byte(page.Content), &data); err != nil { c.JSON(http.StatusOK, defaultHomepageData()) return } } else { data = defaultHomepageData() } c.JSON(http.StatusOK, data) } // UpdateHomepage 更新站点首页数据 func UpdateHomepage(c *gin.Context) { siteID := c.Param("site_id") if siteID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"}) return } var data models.HomepageData if err := c.ShouldBindJSON(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } body, err := json.Marshal(data) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() coll := config.GetDB(config.DBName).Collection("pages") now := time.Now().Format(time.RFC3339) filter := bson.M{"site_id": siteID, "slug": homepageSlug} update := bson.M{ "$set": bson.M{ "site_id": siteID, "slug": homepageSlug, "title": data.Title, "type": "homepage", "content": string(body), "updated_at": now, }, } opts := options.UpdateOne().SetUpsert(true) _, err = coll.UpdateOne(ctx, filter, update, opts) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "保存成功"}) } // DownloadHomepage 下载首页 HTML func DownloadHomepage(c *gin.Context) { siteID := c.Param("site_id") if siteID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"}) return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() coll := config.GetDB(config.DBName).Collection("pages") var page models.Page var data models.HomepageData err := coll.FindOne(ctx, bson.M{"site_id": siteID, "slug": homepageSlug}).Decode(&page) if err == nil && page.Content != "" { _ = json.Unmarshal([]byte(page.Content), &data) } if err != nil || page.Content == "" { data = defaultHomepageData() } html := renderHomepageHTML(&data) c.Header("Content-Disposition", "attachment; filename=index.html") c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html)) } func defaultHomepageData() models.HomepageData { return models.HomepageData{ LogoText: "宇恒一号", NavLinks: []models.NavLink{{Label: "产品简介", URL: "#intro"}, {Label: "产品视频", URL: "#videos"}, {Label: "联系我们", URL: "#contact"}}, Title: "宇恒一号", Subtitle: "", Description: "", DownloadText: "下载", DownloadURL: "#", Platforms: []models.PlatformItem{}, Version: "", LaunchYear: "发布日期:以官网为准", BadgeText: "完全免费", DownloadWindowsURL: "/promotion/downloads/yuheng-windows.zip", DownloadAndroidURL: "/promotion/downloads/yuheng-android.apk", Features: []models.FeatureItem{ {Title: "星际导航", Desc: "先进的 AI 导航系统,精准定位您的需求,引领探索之旅"}, {Title: "量子同步", Desc: "跨维度数据同步技术,您的数据在多宇宙中保持一致"}, {Title: "星际防护", Desc: "来自未来的安全加密协议,守护您的数字资产安全"}, }, FooterText: "© 2024 宇恒一号 · 成都宇信达智能科技有限公司", } } // renderHomepageHTML 根据数据生成首页 HTML(简化版,保留原样式与结构) func renderHomepageHTML(d *models.HomepageData) string { if d == nil { d = &models.HomepageData{} } titleChars := splitTitle(d.Title) navHTML := "" for _, l := range d.NavLinks { navHTML += `` + escape(l.Label) + `` } platformsHTML := "" for _, p := range d.Platforms { platformsHTML += `
` + escape(p.Name) + `
` } featuresHTML := "" for i, f := range d.Features { iconPath := []string{ "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z", "M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z", "M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z", } if i >= len(iconPath) { iconPath = append(iconPath, iconPath[0]) } path := iconPath[i%3] featuresHTML += `

` + escape(f.Title) + `

` + escape(f.Desc) + `

` } sb := &strings.Builder{} sb.WriteString("\n\n\n\n\n") sb.WriteString(escape(d.Title)) sb.WriteString(" - 星际探索版\n") sb.WriteString(homepageCSS) sb.WriteString("\n\n
\n
\n
\n
\n\n
\n

") for _, ch := range titleChars { sb.WriteString("" + escape(ch) + "") } sb.WriteString("

\n
\n

") sb.WriteString(escape(d.Subtitle)) sb.WriteString("

\n

") sb.WriteString(strings.ReplaceAll(escape(d.Description), "\n", "
\n")) sb.WriteString("

\n
\n
\n\n\n") sb.WriteString(escape(d.DownloadText)) sb.WriteString("\n\n
\n
") sb.WriteString(platformsHTML) sb.WriteString("
\n
\n") sb.WriteString(escape(d.Version)) sb.WriteString("\n🚀 ") sb.WriteString(escape(d.LaunchYear)) sb.WriteString("\n⚡ ") sb.WriteString(escape(d.BadgeText)) sb.WriteString("\n
\n
") sb.WriteString(featuresHTML) sb.WriteString("
\n
\n\n\n\n") return sb.String() } func splitTitle(s string) []string { var out []rune for _, r := range s { out = append(out, r) } if len(out) == 0 { return []string{"宇", "恒", "一", "号"} } result := make([]string, len(out)) for i, r := range out { result[i] = string(r) } return result } func escape(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") s = strings.ReplaceAll(s, "\"", """) return s } const homepageCSS = ` `