好的,以下是一个完整的方案,包括数据库设计、GORM 模型、用户注册和激活功能的实现,以及通过邮件发送激活码的过程。

GORM 模型

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "net/smtp"
    "time"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

// 定义User模型
type User struct {
    ID             uint      `gorm:"primaryKey"`
    Username       string    `gorm:"unique"`
    Email          string    `gorm:"unique"`
    Password       string
    SubscriptionID uint
    Subscription   Subscription
    IsActive       bool
    CreatedAt      time.Time
}

// 定义Subscription模型
type Subscription struct {
    ID        uint   `gorm:"primaryKey"`
    Name      string
    CreatedAt time.Time
}

// 定义Resource模型
type Resource struct {
    ID           uint   `gorm:"primaryKey"`
    Name         string
    ResourceType string
    CreatedAt    time.Time
}

// 定义UserResource模型
type UserResource struct {
    ID        uint `gorm:"primaryKey"`
    UserID    uint
    ResourceID uint
    CreatedAt time.Time
}

// 定义Activation模型
type Activation struct {
    ID             uint   `gorm:"primaryKey"`
    UserID         uint
    ActivationCode string `gorm:"size:9"`
    CreatedAt      time.Time
}

// 生成9位激活码
func generateActivationCode() (string, error) {
    b := make([]byte, 6) // 6 bytes * 4/3 = 8 characters
    _, err := rand.Read(b)
    if err != nil {
        return "", err
    }
    return base64.StdEncoding.EncodeToString(b)[:9], nil
}

// 发送激活邮件
func sendActivationEmail(user *User, code string) error {
    from := "your-email@example.com"
    pass := "your-email-password"
    to := user.Email
    activationLink := fmt.Sprintf("http://your-domain.com/activate?code=%s", code)

    msg := fmt.Sprintf("To: %s\r\n"+
        "Subject: Please activate your account\r\n"+
        "\r\n"+
        "Click the following link to activate your account:\r\n"+
        "%s\r\n", to, activationLink)

    return smtp.SendMail("smtp.example.com:587",
        smtp.PlainAuth("", from, pass, "smtp.example.com"),
        from, []string{to}, []byte(msg))
}

func main() {
    dsn := "host=localhost user=gorm dbname=gorm password=gorm sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // 自动迁移表结构
    db.AutoMigrate(&User{}, &Subscription{}, &Resource{}, &UserResource{}, &Activation{})

    // 用户注册示例
    activationCode, err := generateActivationCode()
    if err != nil {
        panic("failed to generate activation code")
    }

    user := User{
        Username:       "test_user",
        Email:          "test_user@example.com",
        Password:       "password", // 在实际应用中应加密存储密码
        SubscriptionID: 1,           // 假设已经有一个 Subscription
    }
    db.Create(&user)

    activation := Activation{
        UserID:         user.ID,
        ActivationCode: activationCode,
    }
    db.Create(&activation)

    // 发送激活邮件
    if err := sendActivationEmail(&user, activationCode); err != nil {
        panic("failed to send activation email")
    }

    fmt.Println("User registered, awaiting activation...")
}

// 激活用户
func activateUser(w http.ResponseWriter, r *http.Request) {
    activationCode := r.URL.Query().Get("code")
    if activationCode == "" {
        http.Error(w, "Missing activation code", http.StatusBadRequest)
        return
    }

    dsn := "host=localhost user=gorm dbname=gorm password=gorm sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        http.Error(w, "Database connection error", http.StatusInternalServerError)
        return
    }

    var activation Activation
    if err := db.Where("activation_code = ?", activationCode).First(&activation).Error; err != nil {
        http.Error(w, "Invalid activation code", http.StatusBadRequest)
        return
    }

    var user User
    db.First(&user, activation.UserID)

    // 更新用户激活状态
    user.IsActive = true
    db.Save(&user)

    // 删除激活记录
    db.Delete(&activation)

    w.Write([]byte("Account activated successfully!"))
}

func main() {
    http.HandleFunc("/activate", activateUser)
    http.ListenAndServe(":8080", nil)
}

数据库设计

users 表

1
2
3
4
5
6
7
8
9
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(255) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    subscription_id INT REFERENCES subscriptions(id),
    is_active BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

subscriptions 表

1
2
3
4
5
CREATE TABLE subscriptions (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL,   -- 'free' 或者 'vip'
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

resources 表

1
2
3
4
5
6
CREATE TABLE resources (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    resource_type VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

user_resources 表

1
2
3
4
5
6
CREATE TABLE user_resources (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id),
    resource_id INT REFERENCES resources(id),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

activations 表

1
2
3
4
5
6
CREATE TABLE activations (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id),
    activation_code VARCHAR(9) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

详细步骤

  1. 数据库设计

    • 创建 users 表保存用户信息。
    • 创建 subscriptions 表定义订阅类型。
    • 创建 resources 表保存资源信息。
    • 创建 user_resources 表保存用户资源使用记录。
    • 创建 activations 表保存激活码和用户的映射关系。
  2. GORM 模型

    • 定义对应的结构体和字段。
    • 初始化数据库连接,并自动迁移表结构。
  3. 生成激活码

    • 使用 crypto/rand 生成 6 个字节的随机数并编码为 Base64 字符串,然后截取前9个字符作为激活码。
  4. 发送激活邮件

    • 使用 SMTP 库发送包含激活链接的邮件,激活链接包括激活码作为查询参数。
  5. 处理用户注册

    • 在用户注册时,生成激活码并创建用户和激活记录。
    • 发送包含激活链接的邮件给注册用户。
  6. 激活用户

    • 定义一个 HTTP 处理函数,处理用户点击激活链接的请求。
    • 根据激活码找到对应的用户,并更新用户的激活状态。
    • 删除激活码记录以确保安全。