Features: - Add article view tracking with automatic deduplication (per user/IP) - Implement intelligent bot detection (35+ patterns: Google, Bing, Baidu, GPTBot, etc) - Create admin analytics dashboard with statistics and filtering - Display view count and comment count on article cards - Add search-based article filter (replaced dropdown for scalability) Analytics Dashboard: - Global stats: total views, human/bot views, unique IPs/users - Top 20 articles ranking with detailed metrics - Detailed view records with time, article, user, IP, user-agent - Filters: article title search, IP search, show/hide bots - Pagination support (50 records per page) Technical Implementation: - Async view recording (non-blocking) - Dual deduplication (application + database layer) - Database indexes for performance optimization - Batch query for comment counts - Full i18n support (Chinese/English) Files Added: - models/article_view.go: View tracking model - models/bot_detector.go: Bot detection logic - handlers/admin_analytics.go: Analytics page handler - templates/admin/analytics_views.html: Analytics UI - test_analytics.sh: Testing script - Documentation: implementation guide, usage guide, changelog Files Modified: - handlers/home.go: Add view recording and comment count queries - models/db.go: Add ArticleView to auto-migration - main.go: Add analytics routes - i18n/i18n.go: Add 35+ translation keys - templates/admin/dashboard.html: Add analytics entry link - templates/pages/home.html: Display view/comment counts on cards Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
165 lines
5.8 KiB
Go
165 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-contrib/sessions/cookie"
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"go_blog/config"
|
|
"go_blog/handlers"
|
|
"go_blog/middleware"
|
|
"go_blog/models"
|
|
)
|
|
|
|
func main() {
|
|
// 1. Load configuration (auto-creates if missing).
|
|
cfg := config.LoadConfig()
|
|
|
|
// 2. Initialize the database (auto-migrates, seeds admin).
|
|
db := models.InitDB(cfg)
|
|
|
|
// 2b. Warm the platform configuration cache from the database.
|
|
models.LoadConfigCache(db)
|
|
|
|
// 3. Create session store (cookie-based).
|
|
store := cookie.NewStore([]byte(cfg.Secret))
|
|
store.Options(sessions.Options{
|
|
Path: "/",
|
|
MaxAge: 86400, // 24 hours
|
|
HttpOnly: true, // prevent XSS access
|
|
Secure: false, // set true in production with HTTPS
|
|
})
|
|
|
|
// 4. Create Gin router.
|
|
router := gin.Default()
|
|
|
|
// 5. Load HTML templates.
|
|
router.LoadHTMLGlob("templates/**/*.html")
|
|
|
|
// 6. Serve uploaded files (avatars etc.) from the storage path.
|
|
router.Static("/uploads", cfg.Path)
|
|
|
|
// 6. Global session middleware.
|
|
router.Use(sessions.Sessions("blog_session", store))
|
|
|
|
// 7. Global context middleware (sets IsLoggedIn, Username for templates).
|
|
router.Use(middleware.SetUserContext(db))
|
|
|
|
// 8. Register routes.
|
|
router.GET("/", handlers.HomePage(db))
|
|
router.GET("/api/articles", handlers.HomeArticlesAPI(db))
|
|
router.GET("/rss", handlers.RSSFeed(db))
|
|
router.GET("/feed", handlers.RSSFeed(db))
|
|
router.GET("/login", handlers.LoginPage())
|
|
router.POST("/login", handlers.Login(db))
|
|
router.POST("/logout", handlers.Logout())
|
|
router.GET("/article/:slug", handlers.ArticleDetail(db))
|
|
router.POST("/article/:slug/comments", handlers.PostComment(db))
|
|
|
|
// Protected admin routes (admin role only).
|
|
admin := router.Group("/admin")
|
|
admin.Use(middleware.AuthRequired(), middleware.AdminRequired(db))
|
|
{
|
|
admin.GET("", handlers.AdminDashboard(db))
|
|
admin.GET("/articles", handlers.ArticleListPage(db))
|
|
admin.GET("/articles/new", handlers.ArticleCreatePage(db))
|
|
admin.POST("/articles/new", handlers.ArticleCreate(db))
|
|
admin.GET("/articles/:id/edit", handlers.ArticleEditPage(db))
|
|
admin.POST("/articles/:id/edit", handlers.ArticleUpdate(db))
|
|
admin.POST("/articles/:id/delete", handlers.ArticleDelete(db))
|
|
|
|
}
|
|
|
|
// Protected admin comment management routes (admin role only).
|
|
comments := router.Group("/admin/comments")
|
|
comments.Use(middleware.AuthRequired(), middleware.AdminRequired(db))
|
|
{
|
|
comments.GET("", handlers.CommentListPage(db))
|
|
comments.POST("/:id/approve", handlers.CommentApprove(db))
|
|
comments.POST("/:id/reject", handlers.CommentReject(db))
|
|
comments.POST("/:id/delete", handlers.CommentDelete(db))
|
|
}
|
|
|
|
// Protected admin user-management routes (admin role only).
|
|
users := router.Group("/admin/users")
|
|
users.Use(middleware.AuthRequired(), middleware.AdminRequired(db))
|
|
{
|
|
users.GET("", handlers.UserListPage(db))
|
|
users.GET("/new", handlers.UserCreatePage(db))
|
|
users.POST("/new", handlers.UserCreate(db))
|
|
users.GET("/:id/edit", handlers.UserEditPage(db))
|
|
users.POST("/:id/edit", handlers.UserUpdate(db))
|
|
users.POST("/:id/delete", handlers.UserDelete(db))
|
|
}
|
|
|
|
// Protected article attachment routes (admin role only).
|
|
attachments := router.Group("/admin/articles")
|
|
attachments.Use(middleware.AuthRequired(), middleware.AdminRequired(db))
|
|
{
|
|
attachments.POST("/attachments", handlers.UploadAttachment(db, cfg.Path))
|
|
attachments.POST("/attachments/:id/delete", handlers.DeleteAttachment(db, cfg.Path))
|
|
attachments.GET("/:id/attachments", handlers.ListAttachments(db))
|
|
}
|
|
|
|
// Protected admin settings routes (platform configuration).
|
|
settings := router.Group("/admin/settings")
|
|
settings.Use(middleware.AuthRequired(), middleware.AdminRequired(db))
|
|
{
|
|
settings.GET("/site", handlers.SiteSettingsPage(db))
|
|
settings.POST("/site", handlers.SiteSettingsSave(db, cfg.Path))
|
|
settings.GET("/upload", handlers.UploadSettingsPage(db))
|
|
settings.POST("/upload", handlers.UploadSettingsSave(db))
|
|
settings.GET("/download", handlers.DownloadSettingsPage(db))
|
|
settings.POST("/download", handlers.DownloadSettingsSave(db))
|
|
settings.GET("/comments", handlers.CommentSettingsPage(db))
|
|
settings.POST("/comments", handlers.CommentSettingsSave(db))
|
|
}
|
|
|
|
// Protected admin analytics routes (reading statistics).
|
|
analytics := router.Group("/admin/analytics")
|
|
analytics.Use(middleware.AuthRequired(), middleware.AdminRequired(db))
|
|
{
|
|
analytics.GET("/views", handlers.ViewAnalyticsPage(db))
|
|
}
|
|
|
|
// Protected profile routes.
|
|
profile := router.Group("/profile")
|
|
profile.Use(middleware.AuthRequired())
|
|
{
|
|
profile.GET("", handlers.ProfilePage(db))
|
|
profile.POST("", handlers.UpdateProfile(db, cfg.Path))
|
|
profile.POST("/avatar", handlers.UploadAvatar(db, cfg.Path))
|
|
}
|
|
|
|
// Protected user article management routes (for non-admin users).
|
|
myArticles := router.Group("/my")
|
|
myArticles.Use(middleware.AuthRequired())
|
|
{
|
|
myArticles.GET("/articles", handlers.MyArticlesPage(db))
|
|
myArticles.GET("/articles/new", handlers.MyArticleCreatePage(db))
|
|
myArticles.POST("/articles/new", handlers.MyArticleCreate(db))
|
|
myArticles.GET("/articles/:id/edit", handlers.MyArticleEditPage(db))
|
|
myArticles.POST("/articles/:id/edit", handlers.MyArticleUpdate(db))
|
|
myArticles.POST("/articles/:id/delete", handlers.MyArticleDelete(db))
|
|
}
|
|
|
|
// Protected article attachment routes for user articles.
|
|
myAttachments := router.Group("/my/articles")
|
|
myAttachments.Use(middleware.AuthRequired())
|
|
{
|
|
myAttachments.POST("/attachments", handlers.UploadAttachment(db, cfg.Path))
|
|
myAttachments.POST("/attachments/:id/delete", handlers.DeleteAttachment(db, cfg.Path))
|
|
myAttachments.GET("/:id/attachments", handlers.ListAttachments(db))
|
|
}
|
|
|
|
// 9. Start the server.
|
|
addr := fmt.Sprintf(":%s", cfg.Port)
|
|
log.Printf("Go Blog starting on http://localhost%s", addr)
|
|
if err := router.Run(addr); err != nil {
|
|
log.Fatalf("Failed to start server: %v", err)
|
|
}
|
|
}
|