如何在网站中用Golang搭建社区论坛

如何在网站中用Golang搭建社区论坛 你好,

我维护着这个技术网站。我想为我的网站创建一个社区论坛。有人能告诉我该如何着手进行吗?

3 回复

Discourse 是一个很好的选择,如果你想要一个简单易用的论坛。

更多关于如何在网站中用Golang搭建社区论坛的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我想为我的网站创建一个社区论坛。

市面上有不错的现成论坛。我首先想到的是 DiscourseFlarum

使用Golang搭建社区论坛的实现方案

以下是一个完整的社区论坛实现方案,包含核心功能和代码示例:

1. 项目结构设计

forum/
├── cmd/
│   └── main.go
├── internal/
│   ├── handlers/
│   ├── models/
│   ├── database/
│   └── middleware/
├── templates/
├── static/
└── go.mod

2. 数据库模型定义

// internal/models/forum.go
package models

import (
    "time"
    "gorm.io/gorm"
)

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Username  string    `gorm:"uniqueIndex;not null"`
    Email     string    `gorm:"uniqueIndex;not null"`
    Password  string    `gorm:"not null"`
    CreatedAt time.Time
    Posts     []Post
    Comments  []Comment
}

type Category struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"uniqueIndex;not null"`
    Slug      string    `gorm:"uniqueIndex;not null"`
    Posts     []Post
}

type Post struct {
    ID         uint      `gorm:"primaryKey"`
    Title      string    `gorm:"not null"`
    Content    string    `gorm:"type:text;not null"`
    UserID     uint      `gorm:"not null"`
    CategoryID uint      `gorm:"not null"`
    Views      int       `gorm:"default:0"`
    CreatedAt  time.Time
    UpdatedAt  time.Time
    User       User
    Category   Category
    Comments   []Comment
}

type Comment struct {
    ID        uint      `gorm:"primaryKey"`
    Content   string    `gorm:"type:text;not null"`
    UserID    uint      `gorm:"not null"`
    PostID    uint      `gorm:"not null"`
    CreatedAt time.Time
    User      User
    Post      Post
}

3. 核心路由和处理器

// internal/handlers/forum.go
package handlers

import (
    "net/http"
    "strconv"
    "github.com/gin-gonic/gin"
    "your-project/internal/models"
)

type ForumHandler struct {
    DB *gorm.DB
}

func (h *ForumHandler) CreatePost(c *gin.Context) {
    var post models.Post
    if err := c.ShouldBind(&post); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    userID, _ := c.Get("userID")
    post.UserID = userID.(uint)
    
    if err := h.DB.Create(&post).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusCreated, post)
}

func (h *ForumHandler) GetPosts(c *gin.Context) {
    var posts []models.Post
    categoryID := c.Query("category_id")
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
    offset := (page - 1) * limit
    
    query := h.DB.Preload("User").Preload("Category")
    
    if categoryID != "" {
        query = query.Where("category_id = ?", categoryID)
    }
    
    query = query.Order("created_at DESC").Offset(offset).Limit(limit)
    
    if err := query.Find(&posts).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, posts)
}

func (h *ForumHandler) AddComment(c *gin.Context) {
    postID, _ := strconv.Atoi(c.Param("id"))
    var comment models.Comment
    
    if err := c.ShouldBind(&comment); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    userID, _ := c.Get("userID")
    comment.UserID = userID.(uint)
    comment.PostID = uint(postID)
    
    if err := h.DB.Create(&comment).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusCreated, comment)
}

4. 主程序入口

// cmd/main.go
package main

import (
    "log"
    "github.com/gin-gonic/gin"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "your-project/internal/handlers"
    "your-project/internal/middleware"
)

func main() {
    // 数据库连接
    dsn := "host=localhost user=forum password=forum dbname=forum port=5432 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
    
    // 自动迁移
    db.AutoMigrate(&models.User{}, &models.Category{}, &models.Post{}, &models.Comment{})
    
    // 初始化处理器
    forumHandler := &handlers.ForumHandler{DB: db}
    
    // 设置路由
    r := gin.Default()
    
    // 静态文件
    r.Static("/static", "./static")
    
    // API路由
    api := r.Group("/api")
    {
        api.POST("/register", handlers.Register)
        api.POST("/login", handlers.Login)
        
        auth := api.Group("/")
        auth.Use(middleware.AuthMiddleware())
        {
            auth.POST("/posts", forumHandler.CreatePost)
            auth.POST("/posts/:id/comments", forumHandler.AddComment)
        }
        
        api.GET("/posts", forumHandler.GetPosts)
        api.GET("/posts/:id", forumHandler.GetPost)
        api.GET("/categories", forumHandler.GetCategories)
    }
    
    // 启动服务器
    r.Run(":8080")
}

5. 认证中间件

// internal/middleware/auth.go
package middleware

import (
    "net/http"
    "strings"
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v4"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
            c.Abort()
            return
        }
        
        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
            return []byte("your-secret-key"), nil
        })
        
        if err != nil || !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }
        
        claims := token.Claims.(jwt.MapClaims)
        c.Set("userID", uint(claims["user_id"].(float64)))
        c.Next()
    }
}

6. 数据库初始化脚本

-- 创建基础表结构
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    slug VARCHAR(50) UNIQUE NOT NULL
);

-- 插入默认分类
INSERT INTO categories (name, slug) VALUES 
    ('General', 'general'),
    ('Technical', 'technical'),
    ('Feedback', 'feedback')
ON CONFLICT DO NOTHING;

7. 环境配置

// config/config.go
package config

import (
    "os"
)

type Config struct {
    DatabaseURL string
    JWTSecret   string
    Port        string
}

func Load() *Config {
    return &Config{
        DatabaseURL: getEnv("DATABASE_URL", "postgres://forum:forum@localhost:5432/forum"),
        JWTSecret:   getEnv("JWT_SECRET", "your-secret-key"),
        Port:        getEnv("PORT", "8080"),
    }
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

这个实现方案包含了论坛的核心功能:用户认证、帖子发布、分类管理、评论系统。使用Gin作为Web框架,GORM进行数据库操作,PostgreSQL作为数据库。可以根据silicophilic.com的具体需求进行扩展和定制。

回到顶部