软件技术-零基础-Golang用MongoDB验证用户信息

欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】


如何避免用户重复注册?如何验证用户登录成功?

上一篇文章软件技术-零基础-Golang存储注册信息
人工智能通识-2019年3月专题汇总

检查重复邮箱

如果用户的邮箱已经存在于MongoDB数据库中了,那么我们应该不要重复写入数据,并且告诉用户您已经注册过了

用下面的代码检测用户邮箱是否已经存在,修改register.goHandleFunc部分:

    //访问数据集
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    dbc := tools.MongoDBCLient.Database("myweb").Collection("user")
    defer cancel()

    //验证用户邮箱是否存在
    count, err := dbc.CountDocuments(context.TODO(), bson.M{"Email": ds.Email})
    if err != nil {
        resp = RespDS{Err: 1, Msg: "读取数据库失败。"}
    }
    if count >1 {
        resp = RespDS{Err: 1, Msg: "邮箱已经存在。"}
    }

    //写入数据库
    res, err := dbc.InsertOne(ctx, bson.M{"Email": ds.Email, "Pw": ds.Pw})
    if err != nil {
        resp = RespDS{Err: 1, Msg: "写入数据库失败."}
    }
    resp.Data.Token = res.InsertedID.(primitive.ObjectID).Hex()

注意这几个地方:

  • CountDocuments是查找存在几个符合条件的,如果存在超过0个就表示已经至少有一个重名邮箱了。
  • bson.M{"Email": ds.Email}我们是搜索符合这个条件的用户,也就是重名用户。

如果这时候保存并切换到app.go运行代码,从网页上提交注册,仍然会导致重复写入数据库,这是因为我们并没有阻止下面的代码执行。

仔细回顾我们的register.go文件的HandleFunc方法的流程:

三次检查,不管检查是否通过,都不会停止。而我们知道,任何一次检查不通过,都应该直接向用户返回结果,完全没必要再做后面的检查了。

所以我们期望的是这样的流程(注意橙色的捷径):


实用功能util.go

我们来改进整个注册流程。
流程HanleFunc里最后负责返回响应数据的是这两句:

    dt, _ := json.Marshal(resp)
    w.Write([]byte(string(dt)))

虽然只有两句,但是因为以后也会很经常的使用,所以很不方便。我们采用类似tools.go的办法把这些常用的方法提炼到一个新的/src/app/util/util.go(utility实用功能)中去,作为一个单独的WWrite(WebWrite)函数:

package util

import (
    "app/uds"
    "encoding/json"
    "net/http"
)

//WWrite 向用户返回信息,返回resp和一个错误信息
func WWrite(w http.ResponseWriter, code int, msg string, data interface{}) (uds.Respons, error) {
    resp := uds.Respons{Code: code, Msg: msg, Data: data}
    var err error
    dt, err1 := json.Marshal(resp)
    if err1 != nil {
        err = err1
    }
    _, err2 := w.Write([]byte(string(dt)))
    if err2 != nil {
        err = err2
    }
    return resp, err
}

注意这几个地方:

  • 我们调用了统一数据格式app/uds
  • 这个WWrite我们使用了多个参数(w http.ResponseWriter, code int, msg string, data interface{}),也返回了多个(2个)值(uds.Respons, error),这和最后的return resp, err是一致的。
  • 我们用这些参数拼合成了一个标准的返回给用户的数据格式resp := uds.Respons{Code: code, Msg: msg, Data: data}

这个uds.Respons是什么样的?下面是完整的uds.go

package uds

import "go.mongodb.org/mongo-driver/mongo"

//Tools 定义返回对象
type Tools struct {
    MongoDBCLient *mongo.Client
}

//Respons 定义统一的返回格式
type Respons struct {
    Code int
    Msg  string
    Data interface{}
}

util.gotool.go有什么区别?都是提供通用工具啊。但我习惯把简单的、不用初始化的轻量工具放在util.go里面,而把那些复杂的需要初始化InitRun的工具放在tool.go里面,以后工具多了,其实还需要进步一细分处理。这只是对功能的分割规划,并非是必须的。
应该控制每个代码文件不要太多,三五百行还能忍一下看懂,两三千行代码看到就头晕了,以后改动和维护也会很烦。

改进register.go

有了util.go实用功能我们就可以方便的调用它了,不仅可以替换掉register.go中最后两句,而且可以把数据结构RespDS和很多resp = RespDS{Err: 1, Msg: "读取数据库失败。"}类似的语句简化掉,因为WWrite可以自动组装成标准的Respons结构并返回。

下面是修改后的完整register.go

package register

import (
    "app/uds"
    "app/util"
    "context"
    "encoding/json"
    "net/http"
    "regexp"
    "time"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

var tools = uds.Tools{}

//InitRun 初始化tools
func InitRun(t uds.Tools) {
    tools = t
}

//ReqDS 注册接口的请求数据格式
type ReqDS struct {
    Email string `bson:"Email"`
    Pw    string
}

//HandleFunc 注册接口处理函数
func HandleFunc(w http.ResponseWriter, r *http.Request) {
    ds := ReqDS{}
    json.NewDecoder(r.Body).Decode(&ds)

    mailRe, _ := regexp.Compile(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`)
    pwRe, _ := regexp.Compile(`^[0-9a-zA-Z_@]{6,18}$`)

    if !pwRe.MatchString(ds.Pw) {
        util.WWrite(w, 1, "密码格式错误。", nil)
        return
    }
    if !mailRe.MatchString(ds.Email) {
        util.WWrite(w, 1, "邮箱格式错误。", nil)
        return
    }

    //访问数据集
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    dbc := tools.MongoDBCLient.Database("myweb").Collection("user")
    defer cancel()

    //验证用户邮箱是否存在
    count, err := dbc.CountDocuments(context.TODO(), bson.M{"Email": ds.Email})
    if err != nil {
        util.WWrite(w, 1, "读取数据库失败。", nil)
        return
    }
    if count > 0 {
        util.WWrite(w, 1, "邮箱已存在。", nil)
        return
    }

    //写入数据库
    res, err := dbc.InsertOne(ctx, bson.M{"Email": ds.Email, "Pw": ds.Pw})
    if err != nil {
        util.WWrite(w, 1, "写入数据库失败。", nil)
        return
    }
    d := res.InsertedID.(primitive.ObjectID).Hex()
    util.WWrite(w, 0, "注册成功。", d)
    return
}

注意这里有很多的return,它表示快速结束当前函数HandleFunc,后面的代码不会被运行。
另外这里也做了很多if err != nil {错误检测,避免在出现异常时候没有反应。
这个代码虽然感觉变多了,但实际运行的顺序却更合理了,如果用户输错了邮箱格式,其实只会运行到util.WWrite(w, 1, "邮箱格式错误。", nil)这一行,后面一大片都不会被运行,所以速度快了很多。

代码多未必运行的更慢,反之亦然

更多扩展内容:

  • 如果我们需要把FindOne得到用户信息提取出来,比如说想知道这个邮箱的密码是什么,可以创建一个var u bson.M对象,然后把读取的信息填充进去dbc.FindOne(context.TODO(), bson.M{"Email": ds.Email}).Decode(&b)然后就可以使用b["Pw"]来读取了:
    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    var result bson.M
    err := collection.FindOne(ctx, bson.M{"Name": "a"}).Decode(&result)
    if err != nil {
        log.Fatal(err)
    }
  • 可以使用Find来查找不限数量的符合条件的数据,如果条件也不限制可以使用空的bson.M来做参数:cur, err := collection.Find(ctx, bson.M{}),但如果要使用它读取到的内容就麻烦一些:
    ctx, _ = context.WithTimeout(context.Background(), 30*time.Second)
    cur, err := collection.Find(ctx, bson.M{"Name": "a"})
    if err != nil {
        log.Fatal(err)
    }
    defer cur.Close(ctx)
    for cur.Next(ctx) {
        var result bson.M
        err := cur.Decode(&result)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(result, "-->", result["Value"])
    }
  • 如果我们只是想知道有多少条符合要求的信息,那么我们可以使用CountDocuments方法:
    ctx, _ = context.WithTimeout(context.Background(), 30*time.Second)
    c, _ := collection.CountDocuments(ctx, bson.M{})
    fmt.Println("Count", c)

到这里为止,我们还是在做注册功能(虽然我们的网页叫login.html。。。),下一篇我们正式做登录,从数据库来检查当前用户是否输对了邮箱和密码。


欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】


每个人的智能新时代

如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,欢迎转载~


END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258