功能和背景介绍

在项目的登录功能中,如果在登录时发现用户名和密码在用户表中不存在,会自动将用户名和密码保存在用户表中,创建一个新的用户。

因此,除了使用手机号和验证码登录以外,还支持使用用户名、密码进行登录。

如果首次使用手机号和验证码进行登录,会默认将手机号作为用户名创建新的用户,将用户结构体对象的数据保存在数据库中。

因此,我们有必要创建用户表。

用户数据结构体定义

在项目中,使用结构体定义用户数据结构。结构体定义如下所示:

type Member struct {
Id int64 `xorm:"pk autoincr" json:"id"`
UserName string `xorm:"varchar(20)" json:"user_name"`
Mobile string `xorm:"varchar(11)" json:"mobile"`
Password string `xorm:"varchar(255)" json:"password"`
RegisterTime int64 `xorm:"bigint" json:"register_time"`
Avatar string `xorm:"varchar(255)" json:"avatar"`
Balance float64 `xorm:"double" json:"balance"`
IsActive int8 `xorm:"tinyint" json:"is_active"`
City string `xorm:"varchar(10)" json:"city"`
}

通过定义Member结构体,表示应用的用户信息。通过TAG中的xorm来指定结构体在数据库表中的约束。

ORM映射

通过engine.Sync2方法将Member同步映射成为数据库中的member表:

err = engine.Sync2(new(model.Member),
new(model.SmsCode))
if err != nil {
return nil,err
}

插入数据

当用户获取完验证码,并填写验证码以后,用户点击登录,会发起登录请求。因此,我们需要来完成登录相关的逻辑操作和处理。用户手机号码和验证码登录的接口是api/login_sms,因此我们在已经创建的MemberController中解析短信验证码接口。如下所示:

func (mc *MemberController) Router(engine *gin.Engine) {
...
//发送手机验证码
engine.GET("/api/sendcode", mc.sendSmsCode)
//手机号和短信登录
engine.OPTIONS("/api/login_sms", mc.smsLogin)
}

在MemberController中创建smsLogin方法完成用户手机号和密码登录的逻辑,详细实现如下:

//短信登录
func (mc *MemberController) smsLogin(context *gin.Context) { var smsParam param.SmsLoginParam
err := toolbox.Decode(context.Request.Body, &smsParam) fmt.Println(err.Error()) fmt.Println(context.PostForm("phone"))
fmt.Println(context.Query("code"))
if err != nil {
toolbox.Failed(context, "参数解析错误")
return
} us := service.NewMemberService()
member := us.SmsLogin(smsParam)
if member != nil {
toolbox.Success(context, member)
return
}
toolbox.Failed(context, "登录失败")
}

用户服务层

在MemberService.go文件中,编写SmsLogin方法完成手机号和密码登录。

func (msi *MemberService) SmsLogin(param param.SmsLoginParam) *model.Member {

	dao := dao.NewMemberDao()
sms := dao.ValidateSmsCode(param.Phone, param.Code) if sms == nil || time.Now().Unix()-sms.CreateTime > 300 {
return nil
} member := dao.QueryByPhone(param.Phone)
if member != nil {
return member
} user := model.Member{}
user.UserName = param.Phone
user.Mobile = param.Phone
user.RegisterTime = time.Now().Unix() user.Id = dao.InsertMember(user)
return &user
}

在MemberService中,首先验证手机号和验证码是否正确。如果通过了手机号和验证码的验证,通过手机号查询用户是否已经存在。如果用户记录不存在,则创建新的用户记录并保存到数据库中,如果用户记录已经存在,则表示登录成功,返回用户信息。

数据库操作的MemberDao实现如下

在MemberDao中,实现用户模块的数据库操作。

首先是手机验证码验证功能,如下所示:

func (md *MemberDao) ValidateSmsCode(phone string, code string) *model.SmsCode {
var sms model.SmsCode if err := md.Where(" phone = ? and code = ? ", phone, code).Find(&sms); err != nil {
toolbox.Error(err.Error())
}
return &sms
}

其次是根据手机号查询用户数据库表中是否存在手机号对应的用户,如下所示:

func (md *MemberDao) QueryByPhone(phone string) *model.Member {
var member model.Member if err := md.Where(" phone = ? ", phone).Find(&member); err != nil {
toolbox.Error(err.Error())
}
return &member
}

最后,对于新手机号,新建用户,插入到数据库中:

func (md *MemberDao) InsertMember(member model.Member) int64 {
result, err := md.InsertOne(&member)
if err != nil {
toolbox.Error(err.Error())
}
return result
}

跨域

我们项目是使用gin开发一个接口项目,前端是使用vue+webpack进行开发和编译运行的。

可以通过如下命令运行为大家提供的前端工程代码,在前端项目的根目录下执行:

npm run dev

在浏览器中访问http://localhost:8080即可进入应用的首页,切换到用户登录界面。

同时后端程序的运行端口是8090。

当使用我们上面两节课已经开发完成的手机号+验证码的方式进行用户登录时。会发现遇到一个问题,如下图所示:

之前我们已经开发完成了手机号+验证码登录的功能,并且使用Postman已经测试成功了,为什么现在在浏览器中会出现这个问题呢?

跨域访问的问题

先了解一下什么是跨域访问。

在浏览器中的任意一个页面地址,或者访问后台的api接口url,其实都包含几个相同的部分:

/*
* 1、通信协议:又称protocol,有很多通信协议,比如http, tcp/ip协议等等。
* 2、主机:也就是常说的host。
* 3、端口:即服务所监听的端口号。
* 4、资源路径:端口号后面的内容即是路径。
*/

当在一个页面中发起一个新的请求时,如果通信协议、主机和端口,这三部分内容中的任意一个与原页面的不相同,就被称之为跨域访问。

如,在gin接口项目中,前端使用nodejs开发,运行在8080端口,我们访问的应用首页是:http://localhost:8080。 在使用gin框架开发的api项目中,服务端的监听端口为8090。

一个端口数8080,一个是8090,两者端口不同,因此按照规定,发生了跨域访问。

OPTIONS请求

如上文所述,前端vue开发的功能,使用axios发送POST登录请求。在请求时发生了跨域访问,因此浏览器为了安全起见,会首先发起一个请求测试一下此次访问是否安全,这种测试的请求类型为OPTIONS,又称之为options嗅探,同时在header中会带上origin,用来判断是否有跨域请求权限。

然后服务器相应Access-Control-Allow-Origin的值,该值会与浏览器的origin值进行匹配,如果能够匹配通过,则表示有跨域访问的权限。

跨域访问权限检查通过,会正式发送POST请求。

服务端设置跨域访问

可以在gin服务端,编写程序进行全局设置。通过中间件的方式设置全局跨域访问,用以返回Access-Control-Allow-Origin和浏览器进行匹配。

在服务端编写跨域访问中间件,详细内容如下:

func Cors() gin.HandlerFunc {
return func(context *gin.Context) {
method := context.Request.Method
origin := context.Request.Header.Get("Origin")
var headerKeys []string
for k, _ := range context.Request.Header {
headerKeys = append(headerKeys, k)
}
headerStr := strings.Join(headerKeys, ",")
if headerStr != "" {
headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
} else {
headerStr = "access-control-allow-origin, access-control-allow-headers"
} if origin != "" {
context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
context.Header("Access-Control-Max-Age", "172800")
context.Header("Access-Control-Allow-Credentials", "false")
context.Set("content-type", "application/json") //// 设置返回格式是json
} if method == "OPTIONS" {
context.JSON(http.StatusOK, "Options Request!")
}
//处理请求
context.Next()
}
}

其中的Access-Control-Allow-Origin的设置,表示允许进行跨域访问,*表示可以访问所有域。同时,通过Header方法进行了其他的设置。

最后context.Next()是中间件使用的标准用法,表示继续处理请求。

服务器设置跨域调用

在main函数中,调用编写好的跨域访问。调用如下:

func main(){
...
app := gin.Default()
app.Use(Cors())
...
} /*
调用app.Use方法,设置跨域访问
*/
功能演示

服务器设置好跨域访问以后,重新启动服务器api程序,并在浏览器端重新访问。可以看到正常发送了OPTIONS嗅探后,正常发送了POST请求。如下图所示:

03 . Gin+Vue开发一个线上外卖应用(用户数据创建,插入,跨域处理)的更多相关文章

  1. 01 . Go之Gin+Vue开发一个线上外卖应用

    项目介绍 我们将开始使用Gin框架开发一个api项目,我们起名为:云餐厅.如同饿了么,美团外卖等生活服务类应用一样,云餐厅是一个线上的外卖应用,应用的用户可以在线浏览商家,商品并下单. 该项目分为客户 ...

  2. 02 . 02 . Go之Gin+Vue开发一个线上外卖应用(集成第三方发送短信和xorm生成存储数据库表)

    集成第三方发送短信 介绍 用户登录 用户登录有两种方式: 短信登录,密码登录 短信登录是使用手机号和验证码进行登录 短信平台 很多云平台,比如阿里云,腾讯云,七牛云等云厂商,向程序开发者提供了短信验证 ...

  3. 04 . Go+Vue开发一个线上外卖应用(用户名密码和图形验证码)

    图形化验证码生成和验证 功能介绍 在使用用户名和密码登录功能时,需要填写验证码,验证码是以图形化的方式进行获取和展示的. 验证码使用原理 验证码的使用流程和原理为:在服务器端负责生成图形化验证码,并以 ...

  4. 05 . Go+Vue开发一个线上外卖应用(Session集成及修改用户头像到Fastdfs)

    用户头像上传 功能介绍 在用户中心中,允许用户更换自己的头像.因此,我们开发上传一张图片到服务器,并保存成为用户的头像. 接口解析 在用户模块的控制器MemberController中,解析头像上传的 ...

  5. 用vue开发一个app(4,一个久等了的文章)H5直播平台登录注册(1)

    我上一篇关于vue的文章和这一篇时间隔了有点久了.最近终于写完了. 因为我一直想写个有点实绩的东西,而不是随便写一个教程一样东西.结合最近在项目中学到的经验和我的一点创意. 首先介绍下这是个什么! H ...

  6. 用Vue开发一个实时性时间转换功能,看这篇文章就够了

    前言 最近有一个说法,如果你看见某个网站的某个功能,你就大概能猜出背后的业务逻辑是怎么样的,以及你能动手开发一个一毛一样的功能,那么你的前端技能算是进阶中高级水平了.比如咱们今天要聊的这个话题:如何用 ...

  7. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 是否有一个全局视角来查看系统的运行状况? 有什么办法可以监控到JVM的实时运行状态?

    https://alibaba.github.io/arthas/ Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决 ...

  8. 基于Vue开发的门户网站展示和后台数据管理系统

    基于Vue的前端框架有很多,这几年随着前端技术的官方应用,总有是学不完的前端知识在等着我们,一个人的精力也是有限,不可能一一掌握,不过我们学习很大程度都会靠兴趣驱动,或者目标导向,最终是可以以点破面, ...

  9. 在Web.Config文件中使用configSource,避免动态修改web.config导致asp.net重启(另添加一个Config文件用于管理用户数据)

    原文:在Web.Config文件中使用configSource,避免动态修改web.config导致asp.net重启(另添加一个Config文件用于管理用户数据) 我们都知道,在asp.net中修改 ...

随机推荐

  1. 推荐条+fragment

    主布局 package com.example.dell.day1215; import android.support.design.widget.TabLayout; import android ...

  2. mongodb安装教程(亲测有效)

    网上太多教程了,都是说的不明不白,所以自己整理一份 #参考官网: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat ...

  3. Centos-转换或复制文件-dd

    dd 转换或复制文件,同时可以对设备进行备份 相关选项 if 输入文件,可以是设备 of   输出文件,可以是输出设备 bs   指定一个block大小,默认为 512字节 count  指定bs数量

  4. 开源后台系统*mee-admin*

    mee-admin开源后台系统 Preface 这是一个开放的时代,我们不能总是把东西揣在口袋里面自己乐呵. 也正如名言所说的"如果你有两块面包,你当用其中一块去换一朵水仙花" 所 ...

  5. Java知识系统回顾整理01基础01第一个程序03Eclipse下载安装

    Eclipse是最流行的java 集成开发环境IDE(Integrated Development Environment) 下载安装Eclipse两种方式 一.方式1:Eclipse官网下载安装 链 ...

  6. 02 Writing Your First Program 写你的第一个C程序

    Let's print "Hi" 打印输出"Hi" In your first computer program, let's print something ...

  7. 05 C语言的数据类型

    C语言的数据类型 在C 中,数据类型是用来声明不同类型的变量或函数的一个广泛的概念.变量的数据类型决定了变量存储占用的空间大小,以及如何去解释存储的位模式. C 中的数据类型可分为以下几大类: 序号 ...

  8. LCD1602 库函数

    LCD1602 库函数 This library allows an Arduino board to control LiquidCrystal displays (LCDs) based on t ...

  9. P3419 [POI2005]SAM-Toy Cars / SP688 SAM - Toy Cars

    一道很妙的贪心题 题面 我们考虑当我们插入时会面临的两种情况 当地上的玩具,不满 \(k\) 个时,那我们直接放就可以了. 当满了 \(k\) 个的时候,我们就要从地上拿出一个来给当前的腾位置. 这就 ...

  10. 初学者的Android移植:在Debian上建立一个稳定的构建环境

    介绍 通过在chrooted环境中设置开发环境,避免依赖冲突和沙箱您的Android开发从您的Debian GNU/Linux系统.这是为通配符类别准备的,因为从源代码构建Android似乎没有在其他 ...