创建一个由go+pgsql+jwt+gorm搭建的restapi

发布时间 2023-05-10 00:05:02作者: 起风了zzz

 

步骤如下:

1. 创建数据库

首先安装并启动Postgresql,然后使用pgAdmin或psql命令行创建一个数据库。例如,我们创建一个名为"testdb"的数据库:

```
CREATE DATABASE testdb;
```

2. 安装Go和相关库

安装Go和相关的库,包括jwt-go和gorm:

```
go get github.com/dgrijalva/jwt-go
go get github.com/jinzhu/gorm
```

3. 初始化GORM模型

我们使用GORM来管理数据库连接和操作。我们需要定义我们的模型。例如,我们定义一个用户模型:

```
type User struct {
ID uint `gorm:"primary_key"`
Email string `gorm:"unique_index"`
Password string
}
```

4. 设置REST API路由

下一步是设置我们的REST API路由。使用mux或gorilla/mux来定义和管理路由。我们需要以下路由:

- /signup:用户注册
- /login:用户登录
- /logout:用户注销

我们可以使用以下代码来定义这些路由:

```
router := mux.NewRouter()
router.HandleFunc("/signup", SignupHandler).Methods("POST")
router.HandleFunc("/login", LoginHandler).Methods("POST")
router.HandleFunc("/logout", LogoutHandler).Methods("POST")
```

5. 注册用户

我们需要为用户注册创建一个路由处理程序。在这个处理程序中,我们使用GORM将用户添加到数据库中。密码应该是散列密码而不是明文密码。在这个处理程序中,我们还可以为新用户生成JWT令牌。以下是示例代码:

```
func SignupHandler(w http.ResponseWriter, r *http.Request) {
// Parse request body
decoder := json.NewDecoder(r.Body)
var user User
err := decoder.Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user.Password = string(hashedPassword)

// Save user
db, err := gorm.Open("postgres", "dbname=testdb sslmode=disable")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer db.Close()
err = db.Create(&user).Error
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Generate JWT token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"email": user.Email,
"id": user.ID,
})
tokenString, err := token.SignedString([]byte("mysecretkey"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Respond with token
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"token": tokenString,
})
}
```

在上面的代码中,我们首先解析请求体。然后我们使用bcrypt哈希算法来散列用户的密码。接下来,我们使用GORM将用户保存到数据库中。最后,我们生成JWT令牌并将其返回给客户端。

6. 用户登录

我们需要为用户登录创建一个路由处理程序。在这个处理程序中,我们需要验证用户提供的凭据,然后生成JWT令牌。以下是示例代码:

```
func LoginHandler(w http.ResponseWriter, r *http.Request) {
// Parse request body
decoder := json.NewDecoder(r.Body)
var user User
err := decoder.Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Find user
db, err := gorm.Open("postgres", "dbname=testdb sslmode=disable")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer db.Close()
var savedUser User
err = db.Where(&User{Email: user.Email}).First(&savedUser).Error
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

// Validate password
err = bcrypt.CompareHashAndPassword([]byte(savedUser.Password), []byte(user.Password))
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

// Generate JWT token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"email": savedUser.Email,
"id": savedUser.ID,
})
tokenString, err := token.SignedString([]byte("mysecretkey"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Respond with token
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"token": tokenString,
})
}
```

在上面的代码中,我们首先解析请求体。然后我们使用GORM查找与提供的电子邮件地址关联的用户。接下来,我们验证用户提供的密码并生成JWT令牌。最后,我们将令牌返回给客户端。

7. 用户注销

我们需要为用户注销创建一个路由处理程序。在这个处理程序中,我们只需要使JWT令牌失效即可。以下是示例代码:

```
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
// TODO: Invalidate JWT token
w.WriteHeader(http.StatusOK)
}
```

上面的代码是一个占位符。在这里,我们只需要将HTTP状态码设置为200(OK)。

8. 验证JWT令牌

在我们的REST API的每个安全路由中,我们都需要验证JWT令牌以确保用户已登录。我们将使用jwt-go库来完成这个任务。以下是示例代码:

```
func SecureHandler(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "Authorization header is missing", http.StatusUnauthorized)
return
}

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte("mysecretkey"), nil
})
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
email := claims["email"].(string)
userID := uint(claims["id"].(float64))
// TODO: Use email and user ID to fetch user from database
w.WriteHeader(http.StatusOK)
return
}

http.Error(w, "Invalid authorization token", http.StatusUnauthorized)
}
```

在上面的代码中,我们首先从Authorization头中获取JWT令牌。然后我们使用jwt-go库来验证令牌。如果令牌被验证且有效,则我们可以从声明中获取用户的电子邮件地址和ID。最后,我们可以使用用户名和ID来查找用户。如果令牌无效,则我们返回HTTP状态代码401(未经授权)。

9. 运行REST API

最后一步是运行我们的REST API。在我们的main函数中,我们只需要使用http.ListenAndServe函数来设置我们的REST API的地址和端口:

```
func main() {
router := mux.NewRouter()
router.HandleFunc("/signup", SignupHandler).Methods("POST")
router.HandleFunc("/login", LoginHandler).Methods("POST")
router.HandleFunc("/logout", LogoutHandler).Methods("POST")
router.HandleFunc("/secure", SecureHandler).Methods("GET")
http.ListenAndServe(":8080", router)
}
```

这样我们的REST API就准备好了!