04 . Go+Vue開發一個線上外賣應用(使用者名稱密碼和圖形驗證碼)

men發表於2020-11-03

圖形化驗證碼生成和驗證

功能介紹

在使用使用者名稱和密碼登入功能時,需要填寫驗證碼,驗證碼是以圖形化的方式進行獲取和展示的。

驗證碼使用原理

驗證碼的使用流程和原理為:在伺服器端負責生成圖形化驗證碼,並以資料流的形式供前端訪問獲取,同時將生成的驗證碼儲存到全域性的快取中,在本案例中,我們使用redis作為全域性快取,並設定快取失效時間。當使用者使用使用者名稱和密碼進行登入時,進行驗證碼驗證。驗證通過即可繼續進行登入。

驗證碼庫安裝

藉助開源的驗證碼工具庫可以生成驗證碼。
首先,安裝開源的驗證碼生成庫:

go get -u github.com/mojocn/base64Captcha
go get github.com/mojocn/base64Captcha@v1.2.2
驗證碼程式碼示例

在下載後的base64Captcha庫的目錄中,可以看到有_example和_example_redis兩個目錄。第一個example是用於演示生成驗證碼和驗證碼的示例程式碼。

按照示例程式碼的說明,執行程式並在瀏覽器進行埠訪問:

go run main.go
//瀏覽器中訪問:http://localhost:8777

如下圖所示:

通過自定義配置,可以選擇不同的生成驗證碼的引數,並重新整理驗證碼,同時還可以對驗證碼進行驗證。

通過exmaple目錄下的main.go程式可以看到生成驗證碼和驗證驗證碼的邏輯,此處不再贅述。

專案整合驗證碼生成和Redis快取

通常來說,驗證碼都是有一定的實效性的,過期驗證碼也就無效了。

因此,我們考慮在專案中引入Redis作為資料快取。當驗證碼生成後,將驗證碼存放在Redis中,並根據配置檔案對Redis進行設定。

安裝go-redis庫

在專案中使用redis,需要安裝go-redis庫,可以在https://github.com/go-redis/redis中檢視如何下載go-redis和配置。

增加Redis配置

在配置檔案app.json中新增redis配置:

"redis_config": {
    "addr": "127.0.0.1",
    "port": "6379",
    "password": "",
    "db": 0
}

同時,新增RedisConfig結構體定義,如下所示:

type RedisConfig struct {
	Addr string `json:"addr"`
	Port string `json:"port"`
	Password string `json:"password"`
	Db   int    `json:"db"`
}
Redis初始化操

進行了redis配置以後,需要對redis進行初始化。可以封裝redis初始化操作函式如下所示:

type RedisStore struct {
	redisClient *redis.Client
}

var Redis *redis.Client

func InitRediStore() *RedisStore {
	config := GetConfig().RedistConfig

	Redis = redis.NewClient(&redis.Options{
		Addr:     config.Addr + ":" + config.Port,
		Password: config.Password,
		DB:       config.Db,
	})

	customeStore := &RedisStore{Redis}
	base64Captcha.SetCustomStore(customeStore)

	return customeStore
}

同時,為customeStore提供Set和Get兩個方法,如下所示:

func (cs *RedisStore) Set(id string, value string) {
	err := cs.redisClient.Set(id, value, time.Minute*2).Err()
	if err != nil {
		log.Println(err.Error())
	}
}

func (cs *RedisStore) Get(id string, clear bool) string {
	val, err := cs.redisClient.Get(id).Result()
	if err != nil {
		toolbox.Error(err.Error())
		return ""
	}
	if clear {
		err := cs.redisClient.Del(id).Err()
		if err != nil {
			toolbox.Error(err.Error())
			return ""
		}
	}
	return val
}

對Redis進行初始化和定義完成以後,需要在main中呼叫一下初始化操作InitRediStore:

func main(){
    ...
    //Redis配置初始化
	 toolbox.InitRediStore()
    ...
}
驗證碼生成和驗證

本專案中採用的驗證碼的生成庫支援三種驗證碼,分別是:audio,character和digit。我們選擇character型別。

定義Captcha.go檔案,實現驗證碼的生成和驗證碼函式的定義。在進行驗證碼生成時,預設提供驗證碼的配置,並生成驗證碼後返回給客戶端瀏覽器。如下是生成驗證碼的函式定義:

//生成驗證碼
func GenerateCaptchaHandler(ctx *gin.Context) {
	//圖形驗證碼的預設配置
	parameters := base64Captcha.ConfigCharacter{
		Height:             60,
		Width:              240,
		Mode:               3,
		ComplexOfNoiseText: 0,
		ComplexOfNoiseDot:  0,
		IsUseSimpleFont:    true,
		IsShowHollowLine:   false,
		IsShowNoiseDot:     false,
		IsShowNoiseText:    false,
		IsShowSlimeLine:    false,
		IsShowSineLine:     false,
		CaptchaLen:         4,
		BgColor: &color.RGBA{
			R: 3,
			G: 102,
			B: 214,
			A: 254,
		},
	}

	captchaId, captcaInterfaceInstance := base64Captcha.GenerateCaptcha("", parameters)
	base64blob := base64Captcha.CaptchaWriteToBase64Encoding(captcaInterfaceInstance)

	captchaResult := CaptchaResult{Id: captchaId, Base64Blob: base64blob}

	// 設定json響應
	tool.Success(ctx, map[string]interface{}{
		"captcha_result": captchaResult,
	})
}
驗證碼介面解析

圖形化驗證碼是使用者名稱和密碼登入功能的資料,屬於Member模組。因此在MemberController中增加獲取驗證碼的介面解析,如下:

func (mc *MemberController) Router(engine *gin.Engine){
    //獲取驗證碼
    engine.GET("/api/captcha", mc.captcha)
}

測試結果如下,能夠正常獲取到資料:

驗證碼的驗證

同理,可以對客戶端提交的驗證碼進行驗證,具體實現邏輯如下:

//驗證驗證碼是否正確
func CaptchaVerify(r *http.Request) bool {
	
	var captchaResult CaptchaResult
	//接收客戶端傳送來的請求引數
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&captchaResult)
	if err != nil {
		log.Println(err)
	}
	defer r.Body.Close()

	//比較影像驗證碼
	verifyResult := base64Captcha.VerifyCaptcha(captchaResult.Id, captchaResult.VertifyValue)

	return verifyResult
}

使用者名稱密碼登入功能開發

功能介紹

上節課已經完成了驗證碼的生成,本節課來開發使用者名稱、密碼和驗證碼登入功能。

介面和引數解析定義

使用者名稱和密碼的登入介面為:

/api/login_pwd

介面請求型別為POST,介面引數有三個:name,pwd,captcha。其中:captcha為驗證碼。

定義登入引數結構體LoginParam:

//使用者名稱,密碼和驗證碼登入
type LoginParam struct {
	Name     string                `json:"name"` //使用者名稱
	Password string                `json:"pwd"`  //密碼
	Id       string                `json:"id"`// captchaId 驗證碼ID
	Value    string                `json:"value"` //驗證碼
}
邏輯控制層實現登入流程控制
方法解析

在MemberController.go檔案中,編寫方法用於處理使用者名稱密碼登入的解析方法如下所示:

func (mc *MemberController) Router(engine *gin.Engine){
    engine.POST("/api/login_pwd", mc.nameLogin)
}

登入流程程式設計實現

定義新的func並命名為nameLogin,實現登入流程邏輯控制:

//使用者名稱、密碼登入
func (mc *MemberController) nameLogin(context *gin.Context) {

	//1、登入引數解析
	var loginParam param.LoginParam

	err := toolbox.Decode(context.Request.Body, &loginParam)
	if err != nil {
		toolbox.Failed(context, "引數解析失敗")
		return
	}

	//2、驗證驗證碼
	service := impl.NewMemberService()
	validate := toolbox.CaptchaVerify(loginParam.Id, loginParam.Value)
	fmt.Println(validate)
	if !validate {
		toolbox.ValidateFailed(context, "驗證碼不正確, 請重新驗證 ")
		return
	}

	//3、登入
	member := service.Login(loginParam.Name, loginParam.Password)
	if member.Id == 0 {
		toolbox.Failed(context, "登入失敗")
		return
	}
	toolbox.Success(context, &member)
}

在控制層的nameLogin方法中,主要有3個邏輯處理:

  • 1、通過*gin.Context解析請求登入請求攜帶的引數。

  • 2、從攜帶的引數中得到提交的驗證碼資料,呼叫驗證碼判斷驗證碼方法對驗證碼進行判斷。驗證碼驗證失敗或者驗證碼失效,直接返回登入失敗資訊。

  • 3、使用使用者名稱、密碼引數進行登入,判斷登入結果。如果登入成功,返回使用者登入資訊,否則返回登入失敗。

Service層實現

在功能服務層的MemberService檔案中,定義和實現使用者名稱密碼登入的Login方法。詳細實現如下:

//使用者登入: 如果沒有登入過,自動進行登入
func (msi *MemberServiceImpl) Login(name string, password string) *model.Member {

	dao := impl.NewMemberDao()

	//1、先查詢是否已經存在該使用者
	member := dao.Query(name, password)
	if member.Id != 0 {
		return member
	}

	user := model.Member{}
	user.UserName = name
	user.Password = toolbox.EncoderSha256(password)
	user.RegisterTime = time.Now().Unix()

	result := dao.InsertMember(user)
	user.Id = result
	return &user
}

在service層的Login方法中,分為兩步邏輯判斷:

  • 1、先查詢是否已經存在該使用者,如果該用於已經存在,則直接將查詢到的使用者資訊返回。

  • 2、如果使用者不存在,將使用者資訊作為新記錄儲存到資料庫中,新增一條記錄。並返回使用者資訊。

最後,涉及到運算元據庫的兩個方法分別是:Query和InsertMember方法。InsertMember方法之前已經編寫過,只需要重新編寫一個Query方法即可,Query方法實現如下所示:

//根據使用者名稱和密碼查詢使用者記錄
func (mdi *MemberDaoImpl) Query(name string, password string) *model.Member {
	var member model.Member

	password = toolbox.EncoderSha256(password)

	_, err := mdi.Where(" user_name = ? and password = ? ", name, password).Get(&member)
	if err != nil {
		toolbox.Error(err.Error())
		return nil
	}

	return &member
}

相關文章