在本学习笔记中,我们将实现一个简单的用户注册和登录系统,使用Go语言作为后端开发语言,MySQL数据库存储用户信息,以及Redis用于存储用户会话数据。我们将依次介绍环境搭建、数据库连接、路由设计、身份验证、会话管理等步骤。
环境搭建
-
安装Go语言:从官方网站(golang.org/dl/)下载并安装Go…
-
安装MySQL:根据操作系统选择适合的MySQL版本,安装并启动MySQL数据库。
-
安装Redis:根据操作系统选择适合的Redis版本,安装并启动Redis服务器。
-
创建项目目录:在你的工作目录下创建一个新的文件夹,例如
user-authentication。
数据库连接
首先,我们需要建立Go语言与MySQL数据库的连接。使用github.com/go-sql-driver/mysql包来实现。
- 打开终端,进入项目目录:
cd user-authentication
- 安装MySQL驱动:
go get -u github.com/go-sql-driver/mysql
- 在项目根目录创建
database.go文件,用于连接数据库:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func connectDB() (*sql.DB, error) {
// Replace with your MySQL connection details
dsn := "username:password@tcp(localhost:3306)/user_auth"
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
fmt.Println("Connected to the database")
return db, nil
}
路由设计与处理器
接下来,我们将设计路由和处理器来实现用户注册和登录功能。使用github.com/gorilla/mux包来实现路由。
- 安装路由库:
go get -u github.com/gorilla/mux
- 在项目根目录创建
main.go文件,并编写以下代码:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/register", handleRegister).Methods("POST")
r.HandleFunc("/login", handleLogin).Methods("POST")
http.Handle("/", r)
fmt.Println("Server started at :8080")
http.ListenAndServe(":8080", nil)
}
用户注册与登录功能实现
我们将实现用户注册和登录功能,并使用MySQL数据库存储用户信息,使用Redis存储会话数据。
- 创建
user.go文件,并编写以下代码:
package main
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"-"`
}
func handleRegister(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
db, err := connectDB()
if err != nil {
http.Error(w, "Database connection error", http.StatusInternalServerError)
return
}
defer db.Close()
_, err = db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", user.Username, user.Password)
if err != nil {
mysqlErr, ok := err.(*mysql.MySQLError)
if ok && mysqlErr.Number == 1062 {
http.Error(w, "Username already exists", http.StatusBadRequest)
} else {
http.Error(w, "Database error", http.StatusInternalServerError)
}
return
}
fmt.Fprintln(w, "User registered successfully")
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
db, err := connectDB()
if err != nil {
http.Error(w, "Database connection error", http.StatusInternalServerError)
return
}
defer db.Close()
var dbUser User
err = db.QueryRow("SELECT id, password FROM users WHERE username = ?", user.Username).Scan(&dbUser.ID, &dbUser.Password)
if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "User not found", http.StatusNotFound)
} else {
http.Error(w, "Database error", http.StatusInternalServerError)
}
return
}
if user.Password != dbUser.Password {
http.Error(w, "Invalid password", http.StatusUnauthorized)
return
}
// TODO: Generate and store session token in Redis
fmt.Fprintln(w, "Login successful")
}
func main() {
// ...
}
使用Redis进行会话管理
在handleLogin函数中,我们需要生成会话令牌(token),并将用户信息存储在Redis中。这里我们使用github.com/go-redis/redis/v8包来实现。
- 安装Redis库:
go get -u github.com/go-redis/redis/v8
- 在
main.go中引入Redis库:
import (
// ...
"github.com/go-redis/redis/v8"
"golang.org/x/net/context"
"time"
)
- 在
handleLogin函数中实现会话管理:
func handleLogin(w http.ResponseWriter, r *http.Request) {
// ...
// Check if the password is valid
if user.Password != dbUser.Password {
http.Error(w, "Invalid password", http.StatusUnauthorized)
return
}
// Generate a session token
sessionToken := generateToken()
// Store session token in Redis
err = storeSessionToken(dbUser.ID, sessionToken)
if err != nil {
http.Error(w, "Session error", http.StatusInternalServerError)
return
}
fmt.Fprintln(w, "Login successful")
}
func generateToken() string {
return fmt.Sprintf("%d-%d", time.Now().Unix(),
rand.Intn(1000000))
}
func storeSessionToken(userID int, token string) error {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
return client.Set(ctx, fmt.Sprintf("user:%d", userID), token, time.Hour*24).Err()
}
在上述代码中,我们使用generateToken函数生成一个会话令牌,将用户ID和会话令牌存储在Redis中,过期时间为24小时。
文件结构
在Go后端开发中,良好的文件结构可以提高代码的可读性、可维护性和可扩展性。虽然文件结构的设置会因项目的规模和需求而有所不同,但以下是一个常见的建议文件结构示例:
project/
|-- cmd/
| |-- main.go # 应用程序入口
|
|-- internal/
| |-- app/
| | |-- handler.go # HTTP处理器
| | |-- service.go # 业务逻辑
| | |-- repository.go # 数据库访问
| |
| |-- config/
| | |-- config.go # 配置文件加载与解析
| |
| |-- middleware/
| | |-- middleware.go # 自定义中间件
| |
| |-- model/
| |-- user.go # 数据模型定义
|
|-- migrations/
| |-- 202308091200_create_users_table.sql # 数据库迁移脚本
|
|-- scripts/
| |-- setup_db.sh # 数据库初始化脚本
|
|-- static/
| |-- css/
| |-- js/
|
|-- templates/
| |-- index.html # HTML模板文件
|
|-- tests/
| |-- app_test.go # 单元测试文件
|
|-- .gitignore # Git忽略文件配置
|-- go.mod # Go模块文件
|-- go.sum # Go模块依赖校验文件
|-- README.md # 项目说明文档
解释一下上面的结构:
-
cmd/:应用程序入口的目录,通常会有一个main.go文件,用于初始化应用程序和调用启动函数。 -
internal/:内部包含应用程序的核心代码,包括处理HTTP请求的handler,业务逻辑的service,数据库操作的repository,以及其他模块。 -
internal/config/:配置文件的加载与解析,可以将不同环境的配置分开管理。 -
internal/middleware/:自定义的中间件,用于处理请求前后的逻辑,例如身份验证、日志记录等。 -
internal/model/:数据模型的定义,例如数据库表的映射结构。 -
migrations/:数据库迁移脚本的存放目录,用于管理数据库结构的变更。 -
scripts/:存放项目相关的脚本,例如数据库初始化脚本、部署脚本等。 -
static/:静态资源文件,例如CSS、JavaScript等。 -
templates/:模板文件,用于生成动态内容的HTML模板。 -
tests/:测试文件的目录,包括单元测试和集成测试。 -
.gitignore:指定Git版本控制中需要忽略的文件和文件夹。 -
go.mod和go.sum:Go模块文件,用于管理项目的依赖。 -
README.md:项目说明文档,包括项目介绍、安装指南、使用说明等。
请注意,这只是一种示例的文件结构,你可以根据项目的需求进行适当调整。无论如何,保持文件结构的清晰和一致性,都有助于项目的开发和维护。