Go語言Gin框架前后端分離項(xiàng)目開發(fā)實(shí)例
基本數(shù)據(jù)配置
配置文件管理
添加依賴 go get github.com/spf13/viper
,支持 JSON, TOML, YAML, HCL
等格式的配置文件。在項(xiàng)目根目錄下面新建 conf
目錄,然后新建 application.yml
文件,寫入內(nèi)容如下:
server: port: 9988 #啟動應(yīng)用程序的端口號 datasource: #數(shù)據(jù)庫配置信息 driverName: mysql host: 127.0.0.1 port: "3306" database: gin_demo username: root password: rx123456 charset: utf8 loc: Asia/Shanghai
數(shù)據(jù)庫配置
創(chuàng)建 common/database.go
文件,使用 gorm 初始化數(shù)據(jù)庫配置:
package common import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm" "net/url" ) var DB *gorm.DB func InitDB() *gorm.DB { //從配置文件中讀取數(shù)據(jù)庫配置信息 host := viper.GetString("datasource.host") port := viper.Get("datasource.port") database := viper.GetString("datasource.database") username := viper.GetString("datasource.username") password := viper.GetString("datasource.password") charset := viper.GetString("datasource.charset") loc := viper.GetString("datasource.loc") args := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=true&loc=%s", username, password, host, port, database, charset, url.QueryEscape(loc)) fmt.Println(args) db, err := gorm.Open(mysql.Open(args), &gorm.Config{}) if err != nil { fmt.Println(err) panic("failed to connect database, err: " + err.Error()) } DB = db return db }
路由配置
新建 router/routes.go
文件:
package router import ( "github.com/gin-gonic/gin" "middleware" ) func CollectRoute(r *gin.Engine) *gin.Engine { r.Use(middleware.CORSMiddleware(), middleware.RecoverMiddleware()) //使用中間件 r.POST("/api/auth/register", controller.Register) //注冊 r.POST("/api/auth/login", controller.Login) //登錄 r.GET("/api/auth/userinfo", middleware.AuthMiddleware(), controllers.UserDetail) //獲取詳情 return r }
封裝公共方法
新建 response/response.go
文件:
package response import ( "github.com/gin-gonic/gin" "net/http" ) // 封裝的響應(yīng)體 func Response(ctx *gin.Context, httpStatus int, code int, data gin.H, msg string) { ctx.JSON(httpStatus, gin.H{ "code": code, "data": data, "msg": msg, }) } func Success(ctx *gin.Context, data gin.H, msg string) { Response(ctx, http.StatusOK, 200, data, msg) } func Fail(ctx *gin.Context, data gin.H, msg string) { Response(ctx, http.StatusOK, 400, data, msg) }
新建 util/util.go
文件
package util import ( "math/rand" "time" ) // 生成隨機(jī)字符串 func RandomString(n int) string { var letters = []byte("asdfghjklzxcvbnmqwertyuiopASDFGHJKLZXCVBNMQWERTYUIOP") result := make([]byte, n) rand.Seed(time.Now().Unix()) for i := range result { result[i] = letters[rand.Intn(len(letters))] } return string(result) }
數(shù)據(jù)庫模型
數(shù)據(jù)表內(nèi)容
CREATE TABLE `user_infos` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '', `telephone` varchar(11) NOT NULL DEFAULT '', `password` varchar(255) NOT NULL DEFAULT '', `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `deleted_at` datetime(3) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
model文件
新建 model/User.go
文件:
package model import "gorm.io/gorm" type UserInfo struct { gorm.Model //繼承g(shù)orm的Model,里面包含了ID、CreatedAt、UpdatedAt、DeletedAt Name string `gorm:"type:varchar(20);not null"` Telephone string `gorm:"varchar(11);not null;unique"` Password string `gorm:"size:255;not null"` }
DTO文件
DTO就是數(shù)據(jù)傳輸對象(Data Transfer Object)的縮寫;用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對象。
新建 response/user_dto.go
文件:
package response import ( model2 "gin-demo/model" ) type UserDto struct { Name string `json:"name"` Telephone string `json:"telephone"` } // DTO就是數(shù)據(jù)傳輸對象(Data Transfer Object)的縮寫;用于 展示層與服務(wù)層之間的數(shù)據(jù)傳輸對象 func ToUserDto(user model2.UserInfo) UserDto { return UserDto{ Name: user.Name, Telephone: user.Telephone, } }
中間件
錯(cuò)誤異常捕獲中間件
新建 middleware/RecoveryMiddleware.go
文件:
package middleware import ( "fmt" response2 "gin-demo/response" "github.com/gin-gonic/gin" ) func RecoverMiddleware() gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { response2.Fail(c, nil, fmt.Sprint(err)) c.Abort() return } }() } }
跨域中間件
跨域,指的是瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript施加的安全限制。新建 middleware/CORSMiddleware.go
文件:
package middleware import ( "github.com/gin-gonic/gin" "net/http" ) // 跨域中間件 func CORSMiddleware() gin.HandlerFunc { //CORS是跨源資源分享(Cross-Origin Resource Sharing)中間件 return func(ctx *gin.Context) { //指定允許其他域名訪問 //ctx.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080") ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") //跨域:CORS(跨來源資源共享)策略 //預(yù)檢結(jié)果緩存時(shí)間 ctx.Writer.Header().Set("Access-Control-Max-Age", "86400") //允許的請求類型(GET,POST等) ctx.Writer.Header().Set("Access-Control-Allow-Methods", "*") //允許的請求頭字段 ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*") //是否允許后續(xù)請求攜帶認(rèn)證信息(cookies),該值只能是true,否則不返回 ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true") if ctx.Request.Method == http.MethodOptions { ctx.AbortWithStatus(200) } else { ctx.Next() } } }
token認(rèn)證中間件
新建 middleware/AuthMiddleware.go
文件:
package middleware import ( common2 "gin-demo/common" model2 "gin-demo/model" "github.com/gin-gonic/gin" "net/http" "strings" ) // token認(rèn)證中間件(權(quán)限控制) func AuthMiddleware() gin.HandlerFunc { return func(ctx *gin.Context) { auth := "jiangzhou" // 獲取authorization header tokenString := ctx.GetHeader("Authorization") //postman測試:在Headers中添加: key:Authorization;value:jiangzhou:xxx(token值) //fmt.Println(tokenString) //fmt.Println(strings.HasPrefix(tokenString,auth+"")) // 無效的token //if tokenString == "" || !strings.HasPrefix(tokenString, "Bearer ") { //驗(yàn)證token的前綴為: if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") { //驗(yàn)證token的前綴為: ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "權(quán)限不足"}) ctx.Abort() return } index := strings.Index(tokenString, auth+":") //找到token前綴對應(yīng)的位置 tokenString = tokenString[index+len(auth)+1:] //截取真實(shí)的token(開始位置為:索引開始的位置+關(guān)鍵字符的長度+1(:的長度為1)) //fmt.Println("截取之后的數(shù)據(jù):",tokenString) token, claims, err := common2.ParseToken(tokenString) if err != nil || !token.Valid { //解析錯(cuò)誤或者過期等 ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "權(quán)限不足"}) ctx.Abort() return } // 驗(yàn)證通過后獲取claim 中的userId userId := claims.UserId //判定 var user model2.UserInfo common2.DB.First(&user, userId) if user.ID == 0 { //如果沒有讀取到內(nèi)容,說明token值有誤 ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "權(quán)限不足"}) ctx.Abort() return } ctx.Set("user", user) //將key-value值存儲到context中 ctx.Next() } }
JWT
新建 common/jwt.go
文件:
package common import ( model2 "gin-demo/model" "github.com/dgrijalva/jwt-go" "time" ) var jwtKey = []byte("a_secret_key") //證書簽名秘鑰(該秘鑰非常重要,如果client端有該秘鑰,就可以簽發(fā)證書了) type Claims struct { UserId uint jwt.StandardClaims } // 分發(fā)證書 func ReleaseToken(user model2.UserInfo) (string, error) { expirationTime := time.Now().Add(7 * 24 * time.Hour) //截止時(shí)間:從當(dāng)前時(shí)刻算起,7天 claims := &Claims{ UserId: user.ID, StandardClaims: jwt.StandardClaims{ ExpiresAt: expirationTime.Unix(), //過期時(shí)間 IssuedAt: time.Now().Unix(), //發(fā)布時(shí)間 Issuer: "jiangzhou", //發(fā)布者 Subject: "user token", //主題 }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) //生成token tokenString, err := token.SignedString(jwtKey) //簽名 if err != nil { return "", err } return tokenString, nil } // 解析證書 func ParseToken(tokenString string) (*jwt.Token, *Claims, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) { return jwtKey, nil }) return token, claims, err }
控制器
UserController
新建 controllers/UserController.go
文件:
package controllers import ( "fmt" common2 "gin-demo/common" model2 "gin-demo/model" response2 "gin-demo/response" util2 "gin-demo/util" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "net/http" ) // 注冊 func UserRegister(ctx *gin.Context) { var requestUser model2.UserInfo ctx.Bind(&requestUser) name := requestUser.Name telephone := requestUser.Telephone password := requestUser.Password // 數(shù)據(jù)驗(yàn)證 if len(telephone) != 11 { //422 Unprocessable Entity 無法處理的請求實(shí)體 response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "手機(jī)號必須為11位") fmt.Println(telephone, len(telephone)) return } if len(password) < 6 { response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "密碼不能少于6位") return } // 如果名稱沒有傳,給一個(gè)10位的隨機(jī)字符串 if len(name) == 0 { name = util2.RandomString(10) } // 判斷手機(jī)號是否存在 if isTelephoneExist(common2.DB, telephone) { response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "用戶已經(jīng)存在") return } // 創(chuàng)建用戶 //返回密碼的hash值(對用戶密碼進(jìn)行二次處理,防止系統(tǒng)管理人員利用) hashPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { response2.Response(ctx, http.StatusInternalServerError, 500, nil, "加密錯(cuò)誤") return } newUser := model2.UserInfo{ Name: name, Telephone: telephone, Password: string(hashPassword), } common2.DB.Create(&newUser) // 新增記錄 // 發(fā)放token token, err := common2.ReleaseToken(newUser) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500, "msg": "系統(tǒng)異常"}) return } // 返回結(jié)果 response2.Success(ctx, gin.H{"token": token}, "注冊成功") } func UserLogin(ctx *gin.Context) { var requestUser model2.UserInfo ctx.Bind(&requestUser) //name := requestUser.Name telephone := requestUser.Telephone password := requestUser.Password // 數(shù)據(jù)驗(yàn)證 if len(telephone) != 11 { //422 Unprocessable Entity 無法處理的請求實(shí)體 response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "手機(jī)號必須為11位") fmt.Println(telephone, len(telephone)) return } if len(password) < 6 { response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "密碼不能少于6位") return } // 依據(jù)手機(jī)號,查詢用戶注冊的數(shù)據(jù)記錄 var user model2.UserInfo common2.DB.Where("telephone=?", telephone).First(&user) if user.ID == 0 { ctx.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422, "msg": "用戶不存在"}) return } // 判斷密碼收否正確 if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "密碼錯(cuò)誤"}) return } // 發(fā)放token token, err := common2.ReleaseToken(user) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500, "msg": "系統(tǒng)異常"}) return } // 返回結(jié)果 response2.Success(ctx, gin.H{"token": token}, "登錄成功") } func UserDetail(ctx *gin.Context) { user, _ := ctx.Get("user") response2.Success(ctx, gin.H{ "user": response2.ToUserDto(user.(model2.UserInfo))}, "響應(yīng)成功") } func isTelephoneExist(db *gorm.DB, telephone string) bool { var user model2.UserInfo db.Where("telephone=?", telephone).First(&user) //如果沒有查詢到數(shù)據(jù),對于uint數(shù)據(jù),默認(rèn)值為:0 if user.ID != 0 { return true } return false }
運(yùn)行調(diào)試
注冊接口
登錄接口
獲取用戶信息
在header中傳遞token數(shù)據(jù)
構(gòu)建發(fā)布項(xiàng)目
在項(xiàng)目根目錄下執(zhí)行 go build
,然后會生成 gin-demo
的文件,然后可以將這個(gè)二進(jìn)制文件拷貝到任意目錄下,另外需要將項(xiàng)目下面的 conf
目錄也拷貝過去。
然后執(zhí)行 ./gin-demo
即可運(yùn)行服務(wù):
以上代碼參考:https://gitee.com/rxbook/gin-demo
前端VUE調(diào)用接口
準(zhǔn)備了一個(gè)簡單的前端頁面,代碼在https://gitee.com/rxbook/vue-demo1
,本地運(yùn)行:
#安裝依賴 npm install #運(yùn)行 npm run serve
發(fā)布構(gòu)建:
npm run build
構(gòu)建完成后會生成 dist
目錄,然后在nginx中配置虛擬主機(jī):
server { listen 80; server_name vue-demo1.cc; root /home/rx/web_front/vue-demo1/dist; location ^~ /api/ { proxy_pass http://127.0.0.1:9988; } }
配置 /etc/hosts
后,在瀏覽器訪問:
到此這篇關(guān)于Go語言Gin框架前后端分離項(xiàng)目開發(fā)實(shí)例的文章就介紹到這了,更多相關(guān)Gin框架前后端分離內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang-gin-mgo高并發(fā)服務(wù)器搭建教程
這篇文章主要介紹了golang-gin-mgo高并發(fā)服務(wù)器搭建教程,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Beego中ORM操作各類數(shù)據(jù)庫連接方式詳細(xì)示例
這篇文章主要為大家介紹了Beego中ORM操作各類數(shù)據(jù)庫連接方式詳細(xì)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04Go?Excelize?API源碼閱讀SetSheetViewOptions示例解析
這篇文章主要為大家介紹了Go-Excelize?API源碼閱讀SetSheetViewOptions示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08詳解Golang如何優(yōu)雅判斷interface是否為nil
這篇文章主要為大家詳細(xì)介紹了Golang如何優(yōu)雅判斷interface是否為nil的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解下2024-01-01Golang爬蟲及正則表達(dá)式的實(shí)現(xiàn)示例
本文主要介紹了Golang爬蟲及正則表達(dá)式的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12